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/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var path26 = require('path');
|
|
5
|
+
var fs25 = require('fs');
|
|
6
6
|
var os6 = require('os');
|
|
7
7
|
var crypto4 = require('crypto');
|
|
8
8
|
var EventEmitter = require('events');
|
|
@@ -15,7 +15,7 @@ var http = require('http');
|
|
|
15
15
|
var https = require('https');
|
|
16
16
|
var zlib = require('zlib');
|
|
17
17
|
var stream = require('stream');
|
|
18
|
-
var
|
|
18
|
+
var fs5 = require('fs/promises');
|
|
19
19
|
var ignoreFactory = require('ignore');
|
|
20
20
|
var child_process = require('child_process');
|
|
21
21
|
var util = require('util');
|
|
@@ -61,8 +61,8 @@ function _interopNamespace(e) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
64
|
-
var
|
|
65
|
-
var
|
|
64
|
+
var path26__default = /*#__PURE__*/_interopDefault(path26);
|
|
65
|
+
var fs25__default = /*#__PURE__*/_interopDefault(fs25);
|
|
66
66
|
var os6__default = /*#__PURE__*/_interopDefault(os6);
|
|
67
67
|
var crypto4__default = /*#__PURE__*/_interopDefault(crypto4);
|
|
68
68
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
@@ -72,7 +72,7 @@ var dns__default = /*#__PURE__*/_interopDefault(dns);
|
|
|
72
72
|
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
73
73
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
74
74
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
75
|
-
var
|
|
75
|
+
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
76
76
|
var ignoreFactory__namespace = /*#__PURE__*/_interopNamespace(ignoreFactory);
|
|
77
77
|
var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
|
|
78
78
|
var dns2__default = /*#__PURE__*/_interopDefault(dns2);
|
|
@@ -107,12 +107,12 @@ var init_audit_logger = __esm({
|
|
|
107
107
|
constructor(workspacePath, debugMode = false) {
|
|
108
108
|
this.workspacePath = workspacePath;
|
|
109
109
|
this.debugMode = debugMode;
|
|
110
|
-
const cascadeDir =
|
|
111
|
-
if (!
|
|
112
|
-
|
|
110
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
111
|
+
if (!fs25__default.default.existsSync(cascadeDir)) {
|
|
112
|
+
fs25__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
113
113
|
}
|
|
114
|
-
this.keyPath =
|
|
115
|
-
this.dbPath =
|
|
114
|
+
this.keyPath = path26__default.default.join(cascadeDir, "audit_log.key");
|
|
115
|
+
this.dbPath = path26__default.default.join(cascadeDir, "audit_log.db");
|
|
116
116
|
this.initEncryptionKey();
|
|
117
117
|
this.db = new Database__default.default(this.dbPath);
|
|
118
118
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -142,11 +142,11 @@ var init_audit_logger = __esm({
|
|
|
142
142
|
return crypto4__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
143
143
|
}
|
|
144
144
|
initEncryptionKey() {
|
|
145
|
-
if (
|
|
146
|
-
this.encryptionKey =
|
|
145
|
+
if (fs25__default.default.existsSync(this.keyPath)) {
|
|
146
|
+
this.encryptionKey = fs25__default.default.readFileSync(this.keyPath);
|
|
147
147
|
} else {
|
|
148
148
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
149
|
-
|
|
149
|
+
fs25__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
encrypt(text) {
|
|
@@ -224,9 +224,9 @@ var init_audit_logger = __esm({
|
|
|
224
224
|
dumpDebugIfNeeded() {
|
|
225
225
|
if (!this.debugMode) return;
|
|
226
226
|
try {
|
|
227
|
-
const dumpPath =
|
|
227
|
+
const dumpPath = path26__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
228
228
|
const entries = this.getAllLogs();
|
|
229
|
-
|
|
229
|
+
fs25__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
230
230
|
} catch (err) {
|
|
231
231
|
console.error("Failed to dump debug audit logs", err);
|
|
232
232
|
}
|
|
@@ -1908,7 +1908,7 @@ function reconcilePrice(model, live) {
|
|
|
1908
1908
|
}
|
|
1909
1909
|
|
|
1910
1910
|
// src/constants.ts
|
|
1911
|
-
var CASCADE_VERSION = "0.
|
|
1911
|
+
var CASCADE_VERSION = "0.68.0";
|
|
1912
1912
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1913
1913
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1914
1914
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4494,13 +4494,13 @@ var DeadModelStore = class {
|
|
|
4494
4494
|
function fileDeadModelPersistence(filePath) {
|
|
4495
4495
|
return {
|
|
4496
4496
|
load() {
|
|
4497
|
-
if (!
|
|
4498
|
-
const raw = JSON.parse(
|
|
4497
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
4498
|
+
const raw = JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
4499
4499
|
return Array.isArray(raw) ? raw : [];
|
|
4500
4500
|
},
|
|
4501
4501
|
save(records) {
|
|
4502
|
-
|
|
4503
|
-
|
|
4502
|
+
fs25.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
4503
|
+
fs25.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
4504
4504
|
}
|
|
4505
4505
|
};
|
|
4506
4506
|
}
|
|
@@ -4663,7 +4663,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4663
4663
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4664
4664
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4665
4665
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4666
|
-
var DEFAULT_CACHE_FILE =
|
|
4666
|
+
var DEFAULT_CACHE_FILE = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4667
4667
|
function normalizeModelId2(id) {
|
|
4668
4668
|
let s = id.toLowerCase();
|
|
4669
4669
|
const slash = s.lastIndexOf("/");
|
|
@@ -4698,7 +4698,7 @@ var LiveDataProvider = class {
|
|
|
4698
4698
|
if (this.loaded) return;
|
|
4699
4699
|
this.loaded = true;
|
|
4700
4700
|
try {
|
|
4701
|
-
const raw = await
|
|
4701
|
+
const raw = await fs5__default.default.readFile(this.opts.cacheFile, "utf-8");
|
|
4702
4702
|
const cache = JSON.parse(raw);
|
|
4703
4703
|
if (cache.snapshot?.families) {
|
|
4704
4704
|
this.snapshot = cache.snapshot;
|
|
@@ -4810,14 +4810,14 @@ var LiveDataProvider = class {
|
|
|
4810
4810
|
}
|
|
4811
4811
|
async saveCache() {
|
|
4812
4812
|
try {
|
|
4813
|
-
await
|
|
4813
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
4814
4814
|
const cache = {
|
|
4815
4815
|
fetchedAt: this.fetchedAt,
|
|
4816
4816
|
snapshot: this.snapshot ?? void 0,
|
|
4817
4817
|
prices: Object.fromEntries(this.prices),
|
|
4818
4818
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4819
4819
|
};
|
|
4820
|
-
await
|
|
4820
|
+
await fs5__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4821
4821
|
} catch {
|
|
4822
4822
|
}
|
|
4823
4823
|
}
|
|
@@ -6602,6 +6602,79 @@ function describeProviderError(c, modelId) {
|
|
|
6602
6602
|
}
|
|
6603
6603
|
}
|
|
6604
6604
|
|
|
6605
|
+
// src/core/verification/acceptance.ts
|
|
6606
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
6607
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
6608
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
6609
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
6610
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
6611
|
+
function targetFile(criterion, ownedFiles) {
|
|
6612
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
6613
|
+
if (mentioned.length > 1) return null;
|
|
6614
|
+
if (mentioned.length === 1) {
|
|
6615
|
+
const candidate = mentioned[0];
|
|
6616
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
6617
|
+
}
|
|
6618
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
6619
|
+
}
|
|
6620
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
6621
|
+
const results = [];
|
|
6622
|
+
for (const criterion of criteria) {
|
|
6623
|
+
const text = criterion.trim();
|
|
6624
|
+
if (!text) {
|
|
6625
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6626
|
+
continue;
|
|
6627
|
+
}
|
|
6628
|
+
if (NEGATION.test(text)) {
|
|
6629
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6630
|
+
continue;
|
|
6631
|
+
}
|
|
6632
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
6633
|
+
const wantsExists = EXISTS.test(text);
|
|
6634
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
6635
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
6636
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6637
|
+
continue;
|
|
6638
|
+
}
|
|
6639
|
+
const file = targetFile(text, ownedFiles);
|
|
6640
|
+
if (!file) {
|
|
6641
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
const stat = await probe.stat(file);
|
|
6645
|
+
if (!stat) {
|
|
6646
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
6647
|
+
continue;
|
|
6648
|
+
}
|
|
6649
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
6650
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
if (needle) {
|
|
6654
|
+
const content = await probe.read(file);
|
|
6655
|
+
if (content === null) {
|
|
6656
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6657
|
+
continue;
|
|
6658
|
+
}
|
|
6659
|
+
const found = content.includes(needle);
|
|
6660
|
+
results.push({
|
|
6661
|
+
criterion,
|
|
6662
|
+
verdict: found ? "passed" : "failed",
|
|
6663
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
6664
|
+
});
|
|
6665
|
+
continue;
|
|
6666
|
+
}
|
|
6667
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
6668
|
+
}
|
|
6669
|
+
return results;
|
|
6670
|
+
}
|
|
6671
|
+
function undecided(results) {
|
|
6672
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
6673
|
+
}
|
|
6674
|
+
function failures(results) {
|
|
6675
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6605
6678
|
// src/core/tiers/t3-worker.ts
|
|
6606
6679
|
var CriticalToolError = class extends Error {
|
|
6607
6680
|
constructor(message, toolName) {
|
|
@@ -6644,7 +6717,8 @@ var KNOWN_TOOLS = [
|
|
|
6644
6717
|
"generate_image",
|
|
6645
6718
|
"generate_video",
|
|
6646
6719
|
"generate_speech",
|
|
6647
|
-
"transcribe_audio"
|
|
6720
|
+
"transcribe_audio",
|
|
6721
|
+
"generate_document"
|
|
6648
6722
|
];
|
|
6649
6723
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
6650
6724
|
"generate_image",
|
|
@@ -6661,18 +6735,45 @@ function buildWorkerRules(has) {
|
|
|
6661
6735
|
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.",
|
|
6662
6736
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
6663
6737
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
6738
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
6739
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
6740
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
6741
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
6742
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
6743
|
+
// alone competes with a file_write habit the model already has.
|
|
6744
|
+
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.',
|
|
6745
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
6746
|
+
// describing a chart that does not exist, which is what real decks came
|
|
6747
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
6748
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
6749
|
+
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.',
|
|
6664
6750
|
// Without this the model "writes the image" as prose — a bracketed
|
|
6665
6751
|
// description sitting in the deck where the picture should be — even with a
|
|
6666
6752
|
// working image model registered. Naming the reference syntax matters as
|
|
6667
6753
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
6668
|
-
// missing image.
|
|
6754
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
6755
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
6756
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
6757
|
+
// silently degrades to caption text.
|
|
6669
6758
|
//
|
|
6670
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
6671
|
-
//
|
|
6672
|
-
//
|
|
6673
|
-
//
|
|
6674
|
-
//
|
|
6675
|
-
has("generate_image") &&
|
|
6759
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
6760
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
6761
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
6762
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
6763
|
+
// ever sees.
|
|
6764
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
6765
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
6766
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
6767
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
6768
|
+
// blocks render as real chart objects.
|
|
6769
|
+
'- 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.',
|
|
6770
|
+
// The other half of image reliability, and the half no amount of
|
|
6771
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
6772
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
6773
|
+
// With neither, the model was never given a choice — and a model that
|
|
6774
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
6775
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
6776
|
+
!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.",
|
|
6676
6777
|
// The video counterpart of the image rule above, and the fix for the
|
|
6677
6778
|
// reported "it writes scripts forever and never makes the video" run. The
|
|
6678
6779
|
// failure mode is identical — the model narrates the deliverable instead of
|
|
@@ -6682,7 +6783,7 @@ function buildWorkerRules(has) {
|
|
|
6682
6783
|
// the deliverable rather than an illustration inside one, and the reference
|
|
6683
6784
|
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
6684
6785
|
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.',
|
|
6685
|
-
has("run_code") &&
|
|
6786
|
+
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.`,
|
|
6686
6787
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
6687
6788
|
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.',
|
|
6688
6789
|
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.",
|
|
@@ -6693,13 +6794,34 @@ function buildWorkerRules(has) {
|
|
|
6693
6794
|
Rules:
|
|
6694
6795
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
6695
6796
|
}
|
|
6696
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
6797
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
6798
|
+
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;
|
|
6697
6799
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
6698
6800
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
6699
6801
|
if (assignment?.files?.length) return true;
|
|
6700
6802
|
const haystack = `${assignment?.description ?? ""}
|
|
6701
6803
|
${assignment?.expectedOutput ?? ""}`;
|
|
6702
|
-
return
|
|
6804
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6805
|
+
}
|
|
6806
|
+
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;
|
|
6807
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
6808
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
6809
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
6810
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
6811
|
+
if (!canVisualize) return null;
|
|
6812
|
+
const haystack = `${assignment?.description ?? ""}
|
|
6813
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
6814
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
6815
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
6816
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
6817
|
+
const options = [];
|
|
6818
|
+
if (toolNames.includes("generate_image")) {
|
|
6819
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
6820
|
+
}
|
|
6821
|
+
if (toolNames.includes("generate_document")) {
|
|
6822
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
6823
|
+
}
|
|
6824
|
+
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.`;
|
|
6703
6825
|
}
|
|
6704
6826
|
var T3Worker = class extends BaseTier {
|
|
6705
6827
|
router;
|
|
@@ -6869,7 +6991,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6869
6991
|
}
|
|
6870
6992
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
6871
6993
|
if (!artifactCheck.ok) {
|
|
6872
|
-
correctionAttempts
|
|
6994
|
+
correctionAttempts++;
|
|
6873
6995
|
issues.push(...artifactCheck.issues);
|
|
6874
6996
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
6875
6997
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -6880,13 +7002,47 @@ Now execute your subtask using this context where relevant.`
|
|
|
6880
7002
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6881
7003
|
}
|
|
6882
7004
|
}
|
|
7005
|
+
const visualIssue = missingVisualEvidence(
|
|
7006
|
+
assignment,
|
|
7007
|
+
output,
|
|
7008
|
+
toolCalls.map((t) => t.name),
|
|
7009
|
+
this.tools.map((t) => t.name)
|
|
7010
|
+
);
|
|
7011
|
+
if (visualIssue) {
|
|
7012
|
+
correctionAttempts++;
|
|
7013
|
+
issues.push(visualIssue);
|
|
7014
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
7015
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
7016
|
+
}
|
|
7017
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
7018
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
7019
|
+
if (acceptanceFailures.length > 0) {
|
|
7020
|
+
correctionAttempts++;
|
|
7021
|
+
issues.push(...acceptanceFailures);
|
|
7022
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
7023
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
7024
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
7025
|
+
if (recheck.length > 0) {
|
|
7026
|
+
issues.push(...recheck);
|
|
7027
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
7028
|
+
failed.push(...recheck);
|
|
7029
|
+
this.setStatus("FAILED");
|
|
7030
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
7031
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
for (const result2 of acceptanceResults) {
|
|
7035
|
+
if (result2.verdict === "undecidable") continue;
|
|
7036
|
+
checksRun.push(result2.criterion);
|
|
7037
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
7038
|
+
}
|
|
6883
7039
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6884
|
-
const testResult = await this.selfTest(assignment, output);
|
|
7040
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
6885
7041
|
checksRun.push(...testResult.checksRun);
|
|
6886
7042
|
passed.push(...testResult.passed);
|
|
6887
7043
|
failed.push(...testResult.failed);
|
|
6888
7044
|
if (testResult.failed.length > 0) {
|
|
6889
|
-
correctionAttempts
|
|
7045
|
+
correctionAttempts++;
|
|
6890
7046
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
6891
7047
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
6892
7048
|
output = corrected;
|
|
@@ -7194,8 +7350,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7194
7350
|
// Media generation can run for a minute; without this a cancelled run
|
|
7195
7351
|
// still pays for an image nobody will see.
|
|
7196
7352
|
...this.signal ? { signal: this.signal } : {},
|
|
7197
|
-
saveSnapshot: async (
|
|
7198
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7353
|
+
saveSnapshot: async (path28, content) => {
|
|
7354
|
+
this.store?.addFileSnapshot(this.taskId, path28, content);
|
|
7199
7355
|
},
|
|
7200
7356
|
sendPeerSync: (to, syncType, content) => {
|
|
7201
7357
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7371,7 +7527,7 @@ ${assignment.expectedOutput}`;
|
|
|
7371
7527
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7372
7528
|
const haystack = `${assignment.description}
|
|
7373
7529
|
${assignment.expectedOutput}`;
|
|
7374
|
-
const matches = haystack.match(
|
|
7530
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
7375
7531
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
7376
7532
|
}
|
|
7377
7533
|
async verifyArtifacts(assignment) {
|
|
@@ -7382,9 +7538,9 @@ ${assignment.expectedOutput}`;
|
|
|
7382
7538
|
const { promisify: promisify4 } = await import('util');
|
|
7383
7539
|
const execAsync2 = promisify4(exec2);
|
|
7384
7540
|
for (const artifactPath of artifactPaths) {
|
|
7385
|
-
const absolutePath =
|
|
7541
|
+
const absolutePath = path26__default.default.resolve(process.cwd(), artifactPath);
|
|
7386
7542
|
try {
|
|
7387
|
-
const stat = await
|
|
7543
|
+
const stat = await fs5__default.default.stat(absolutePath);
|
|
7388
7544
|
if (!stat.isFile()) {
|
|
7389
7545
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
7390
7546
|
continue;
|
|
@@ -7393,8 +7549,22 @@ ${assignment.expectedOutput}`;
|
|
|
7393
7549
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
7394
7550
|
continue;
|
|
7395
7551
|
}
|
|
7396
|
-
if (
|
|
7397
|
-
const
|
|
7552
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
7553
|
+
const head = Buffer.alloc(4);
|
|
7554
|
+
const fh = await fs5__default.default.open(absolutePath, "r");
|
|
7555
|
+
try {
|
|
7556
|
+
await fh.read(head, 0, 4, 0);
|
|
7557
|
+
} finally {
|
|
7558
|
+
await fh.close();
|
|
7559
|
+
}
|
|
7560
|
+
if (head.toString("latin1") !== "PK") {
|
|
7561
|
+
issues.push(
|
|
7562
|
+
`${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.`
|
|
7563
|
+
);
|
|
7564
|
+
continue;
|
|
7565
|
+
}
|
|
7566
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
7567
|
+
const content = await fs5__default.default.readFile(absolutePath, "utf-8");
|
|
7398
7568
|
if (!content.trim()) {
|
|
7399
7569
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
7400
7570
|
continue;
|
|
@@ -7403,7 +7573,7 @@ ${assignment.expectedOutput}`;
|
|
|
7403
7573
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7404
7574
|
continue;
|
|
7405
7575
|
}
|
|
7406
|
-
const ext =
|
|
7576
|
+
const ext = path26__default.default.extname(absolutePath).toLowerCase();
|
|
7407
7577
|
try {
|
|
7408
7578
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7409
7579
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7489,14 +7659,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7489
7659
|
}
|
|
7490
7660
|
return current;
|
|
7491
7661
|
}
|
|
7492
|
-
|
|
7662
|
+
/**
|
|
7663
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
7664
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
7665
|
+
*/
|
|
7666
|
+
async checkAcceptance(assignment) {
|
|
7667
|
+
const criteria = assignment.acceptance ?? [];
|
|
7668
|
+
if (!criteria.length) return [];
|
|
7669
|
+
const resolve2 = (target) => path26__default.default.resolve(process.cwd(), target);
|
|
7670
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7671
|
+
stat: async (target) => {
|
|
7672
|
+
try {
|
|
7673
|
+
const stat = await fs5__default.default.stat(resolve2(target));
|
|
7674
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
7675
|
+
} catch {
|
|
7676
|
+
return null;
|
|
7677
|
+
}
|
|
7678
|
+
},
|
|
7679
|
+
read: async (target) => {
|
|
7680
|
+
try {
|
|
7681
|
+
const content = await fs5__default.default.readFile(resolve2(target), "utf-8");
|
|
7682
|
+
return content.includes("\uFFFD") ? null : content;
|
|
7683
|
+
} catch {
|
|
7684
|
+
return null;
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
});
|
|
7688
|
+
}
|
|
7689
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
7690
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
7493
7691
|
const prompt = `Self-test this output against the assignment requirements.
|
|
7494
7692
|
|
|
7495
7693
|
Assignment: ${assignment.description}
|
|
7496
7694
|
Expected output: ${assignment.expectedOutput}
|
|
7497
7695
|
Constraints: ${assignment.constraints.join("; ")}
|
|
7498
|
-
${
|
|
7499
|
-
${
|
|
7696
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
7697
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
7500
7698
|
` : ""}
|
|
7501
7699
|
Output to test:
|
|
7502
7700
|
${output}
|
|
@@ -7563,8 +7761,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
7563
7761
|
} catch {
|
|
7564
7762
|
}
|
|
7565
7763
|
}
|
|
7566
|
-
async correctOutput(originalOutput,
|
|
7567
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
7764
|
+
async correctOutput(originalOutput, failures2) {
|
|
7765
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
7568
7766
|
|
|
7569
7767
|
Original output:
|
|
7570
7768
|
${originalOutput}
|
|
@@ -8225,12 +8423,220 @@ function describeGenerationForPlanner(has) {
|
|
|
8225
8423
|
return lines.join("\n");
|
|
8226
8424
|
}
|
|
8227
8425
|
|
|
8426
|
+
// src/core/orchestration/task-graph.ts
|
|
8427
|
+
var TaskGraphValidationError = class extends Error {
|
|
8428
|
+
constructor(issues) {
|
|
8429
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
8430
|
+
this.issues = issues;
|
|
8431
|
+
this.name = "TaskGraphValidationError";
|
|
8432
|
+
}
|
|
8433
|
+
issues;
|
|
8434
|
+
};
|
|
8435
|
+
function normalizeDependencies(input) {
|
|
8436
|
+
if (!input?.length) return [];
|
|
8437
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
8438
|
+
}
|
|
8439
|
+
function cycleNodes(nodes) {
|
|
8440
|
+
const adjacency = new Map(
|
|
8441
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
8442
|
+
);
|
|
8443
|
+
const index = /* @__PURE__ */ new Map();
|
|
8444
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
8445
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
8446
|
+
const stack = [];
|
|
8447
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
8448
|
+
let counter = 0;
|
|
8449
|
+
const open = (id) => {
|
|
8450
|
+
index.set(id, counter);
|
|
8451
|
+
lowlink.set(id, counter);
|
|
8452
|
+
counter++;
|
|
8453
|
+
stack.push(id);
|
|
8454
|
+
onStack.add(id);
|
|
8455
|
+
};
|
|
8456
|
+
for (const root of nodes) {
|
|
8457
|
+
if (index.has(root.id)) continue;
|
|
8458
|
+
open(root.id);
|
|
8459
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
8460
|
+
while (frames.length > 0) {
|
|
8461
|
+
const frame = frames[frames.length - 1];
|
|
8462
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
8463
|
+
if (frame.edge < neighbours.length) {
|
|
8464
|
+
const next = neighbours[frame.edge];
|
|
8465
|
+
frame.edge++;
|
|
8466
|
+
if (!adjacency.has(next)) continue;
|
|
8467
|
+
if (!index.has(next)) {
|
|
8468
|
+
open(next);
|
|
8469
|
+
frames.push({ id: next, edge: 0 });
|
|
8470
|
+
} else if (onStack.has(next)) {
|
|
8471
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
8472
|
+
}
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
frames.pop();
|
|
8476
|
+
const parent = frames[frames.length - 1];
|
|
8477
|
+
if (parent) {
|
|
8478
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
8479
|
+
}
|
|
8480
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
8481
|
+
const component = [];
|
|
8482
|
+
for (; ; ) {
|
|
8483
|
+
const member = stack.pop();
|
|
8484
|
+
onStack.delete(member);
|
|
8485
|
+
component.push(member);
|
|
8486
|
+
if (member === frame.id) break;
|
|
8487
|
+
}
|
|
8488
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
8489
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8492
|
+
}
|
|
8493
|
+
return inCycle;
|
|
8494
|
+
}
|
|
8495
|
+
function throwIfStrict(mode, issues) {
|
|
8496
|
+
if (mode === "strict" && issues.length > 0) {
|
|
8497
|
+
throw new TaskGraphValidationError(issues);
|
|
8498
|
+
}
|
|
8499
|
+
}
|
|
8500
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
8501
|
+
const mode = options.mode ?? "strict";
|
|
8502
|
+
const issues = [];
|
|
8503
|
+
if (inputNodes.length === 0) {
|
|
8504
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
8505
|
+
throwIfStrict(mode, issues);
|
|
8506
|
+
}
|
|
8507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8508
|
+
const nodes = [];
|
|
8509
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
8510
|
+
const input = inputNodes[ordinal];
|
|
8511
|
+
const id = input.id?.trim();
|
|
8512
|
+
const title = input.title?.trim();
|
|
8513
|
+
if (!id || !title) {
|
|
8514
|
+
issues.push({
|
|
8515
|
+
code: "INVALID_NODE",
|
|
8516
|
+
nodeId: id || void 0,
|
|
8517
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
8518
|
+
});
|
|
8519
|
+
continue;
|
|
8520
|
+
}
|
|
8521
|
+
if (seen.has(id)) {
|
|
8522
|
+
issues.push({
|
|
8523
|
+
code: "DUPLICATE_NODE_ID",
|
|
8524
|
+
nodeId: id,
|
|
8525
|
+
message: `Duplicate task graph node id: ${id}`
|
|
8526
|
+
});
|
|
8527
|
+
continue;
|
|
8528
|
+
}
|
|
8529
|
+
seen.add(id);
|
|
8530
|
+
nodes.push({
|
|
8531
|
+
id,
|
|
8532
|
+
title,
|
|
8533
|
+
kind: input.kind,
|
|
8534
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
8535
|
+
payload: input.payload,
|
|
8536
|
+
ordinal
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8539
|
+
throwIfStrict(mode, issues);
|
|
8540
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
8541
|
+
const sanitized = nodes.map((node) => {
|
|
8542
|
+
const dependsOn = [];
|
|
8543
|
+
for (const dependencyId of node.dependsOn) {
|
|
8544
|
+
if (dependencyId === node.id) {
|
|
8545
|
+
issues.push({
|
|
8546
|
+
code: "SELF_DEPENDENCY",
|
|
8547
|
+
nodeId: node.id,
|
|
8548
|
+
dependencyId,
|
|
8549
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
8550
|
+
});
|
|
8551
|
+
continue;
|
|
8552
|
+
}
|
|
8553
|
+
if (!ids.has(dependencyId)) {
|
|
8554
|
+
issues.push({
|
|
8555
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
8556
|
+
nodeId: node.id,
|
|
8557
|
+
dependencyId,
|
|
8558
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
8559
|
+
});
|
|
8560
|
+
continue;
|
|
8561
|
+
}
|
|
8562
|
+
dependsOn.push(dependencyId);
|
|
8563
|
+
}
|
|
8564
|
+
return { ...node, dependsOn };
|
|
8565
|
+
});
|
|
8566
|
+
throwIfStrict(mode, issues);
|
|
8567
|
+
const cyclic = cycleNodes(sanitized);
|
|
8568
|
+
let repaired = sanitized;
|
|
8569
|
+
if (cyclic.size > 0) {
|
|
8570
|
+
const cycleIssues = [];
|
|
8571
|
+
repaired = sanitized.map((node) => {
|
|
8572
|
+
if (!cyclic.has(node.id)) return node;
|
|
8573
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
8574
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
8575
|
+
cycleIssues.push({
|
|
8576
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
8577
|
+
nodeId: node.id,
|
|
8578
|
+
dependencyId,
|
|
8579
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
8580
|
+
});
|
|
8581
|
+
return false;
|
|
8582
|
+
});
|
|
8583
|
+
return { ...node, dependsOn };
|
|
8584
|
+
});
|
|
8585
|
+
issues.push(...cycleIssues);
|
|
8586
|
+
throwIfStrict(mode, cycleIssues);
|
|
8587
|
+
}
|
|
8588
|
+
const remainingCycle = cycleNodes(repaired);
|
|
8589
|
+
if (remainingCycle.size > 0) {
|
|
8590
|
+
throw new TaskGraphValidationError([
|
|
8591
|
+
{
|
|
8592
|
+
code: "INVALID_NODE",
|
|
8593
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
8594
|
+
}
|
|
8595
|
+
]);
|
|
8596
|
+
}
|
|
8597
|
+
return {
|
|
8598
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
8599
|
+
issues
|
|
8600
|
+
};
|
|
8601
|
+
}
|
|
8602
|
+
|
|
8603
|
+
// src/core/orchestration/adapters.ts
|
|
8604
|
+
function compileSectionGraph(sections) {
|
|
8605
|
+
return compileTaskGraph(
|
|
8606
|
+
sections.map((section) => ({
|
|
8607
|
+
id: section.sectionId,
|
|
8608
|
+
title: section.sectionTitle,
|
|
8609
|
+
kind: "section",
|
|
8610
|
+
dependsOn: section.dependsOn,
|
|
8611
|
+
payload: section
|
|
8612
|
+
})),
|
|
8613
|
+
{ mode: "repair" }
|
|
8614
|
+
);
|
|
8615
|
+
}
|
|
8616
|
+
function compileSubtaskGraph(subtasks) {
|
|
8617
|
+
return compileTaskGraph(
|
|
8618
|
+
subtasks.map((subtask) => ({
|
|
8619
|
+
id: subtask.subtaskId,
|
|
8620
|
+
title: subtask.subtaskTitle,
|
|
8621
|
+
kind: "subtask",
|
|
8622
|
+
dependsOn: subtask.dependsOn,
|
|
8623
|
+
payload: subtask
|
|
8624
|
+
})),
|
|
8625
|
+
{ mode: "repair" }
|
|
8626
|
+
);
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8228
8629
|
// src/core/tiers/t2-manager.ts
|
|
8229
8630
|
function buildT2SystemPrompt(has) {
|
|
8230
8631
|
const generation = describeGenerationForPlanner(has);
|
|
8231
8632
|
return [
|
|
8232
8633
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
8233
|
-
|
|
8634
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
8635
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
8636
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
8637
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
8638
|
+
// real worker call on every small section.
|
|
8639
|
+
"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.",
|
|
8234
8640
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
8235
8641
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8236
8642
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -8626,21 +9032,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8626
9032
|
* then reduce in-degrees of their dependents and repeat.
|
|
8627
9033
|
*/
|
|
8628
9034
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
9035
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
9036
|
+
for (const issue of compiled.issues) {
|
|
9037
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
9038
|
+
}
|
|
9039
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
9040
|
+
const assignment = node.payload;
|
|
9041
|
+
const original = assignment.dependsOn ?? [];
|
|
9042
|
+
const repaired = node.dependsOn;
|
|
9043
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
9044
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
9045
|
+
});
|
|
8629
9046
|
const adj = /* @__PURE__ */ new Map();
|
|
8630
9047
|
const inDegree = /* @__PURE__ */ new Map();
|
|
8631
9048
|
const resultMap = /* @__PURE__ */ new Map();
|
|
8632
|
-
for (const a of
|
|
9049
|
+
for (const a of sanitizedAssignments) {
|
|
8633
9050
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
8634
9051
|
inDegree.set(a.subtaskId, 0);
|
|
8635
9052
|
}
|
|
8636
|
-
for (const a of
|
|
8637
|
-
const
|
|
8638
|
-
for (const dep of deps) {
|
|
9053
|
+
for (const a of sanitizedAssignments) {
|
|
9054
|
+
for (const dep of a.dependsOn ?? []) {
|
|
8639
9055
|
adj.get(dep).add(a.subtaskId);
|
|
8640
9056
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
8641
9057
|
}
|
|
8642
9058
|
}
|
|
8643
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
8644
9059
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
8645
9060
|
let wave = 0;
|
|
8646
9061
|
let respawnBudget = 1;
|
|
@@ -8847,49 +9262,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8847
9262
|
}
|
|
8848
9263
|
return [...resultMap.values()];
|
|
8849
9264
|
}
|
|
8850
|
-
/**
|
|
8851
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
8852
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
8853
|
-
*
|
|
8854
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
8855
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
8856
|
-
*/
|
|
8857
|
-
breakCycles(assignments, adj, inDegree) {
|
|
8858
|
-
const degree = new Map(inDegree);
|
|
8859
|
-
const queue = [];
|
|
8860
|
-
const visited = /* @__PURE__ */ new Set();
|
|
8861
|
-
for (const [id, d] of degree) {
|
|
8862
|
-
if (d === 0) queue.push(id);
|
|
8863
|
-
}
|
|
8864
|
-
while (queue.length > 0) {
|
|
8865
|
-
const id = queue.shift();
|
|
8866
|
-
visited.add(id);
|
|
8867
|
-
for (const dep of adj.get(id) ?? []) {
|
|
8868
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
8869
|
-
degree.set(dep, newDeg);
|
|
8870
|
-
if (newDeg === 0) queue.push(dep);
|
|
8871
|
-
}
|
|
8872
|
-
}
|
|
8873
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
8874
|
-
if (cycleNodes.length === 0) return assignments;
|
|
8875
|
-
this.log(
|
|
8876
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
8877
|
-
);
|
|
8878
|
-
return assignments.map((a) => {
|
|
8879
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
8880
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
8881
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
8882
|
-
this.log(
|
|
8883
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
8884
|
-
);
|
|
8885
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
8886
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
8887
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
8888
|
-
}
|
|
8889
|
-
}
|
|
8890
|
-
return { ...a, dependsOn: safeDeps };
|
|
8891
|
-
});
|
|
8892
|
-
}
|
|
8893
9265
|
async retryT3(assignment, taskId) {
|
|
8894
9266
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
8895
9267
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -9025,6 +9397,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9025
9397
|
}
|
|
9026
9398
|
};
|
|
9027
9399
|
|
|
9400
|
+
// src/core/orchestration/scheduler.ts
|
|
9401
|
+
var DependencyScheduler = class {
|
|
9402
|
+
constructor(graph, hooks, options = {}) {
|
|
9403
|
+
this.graph = graph;
|
|
9404
|
+
this.hooks = hooks;
|
|
9405
|
+
this.options = options;
|
|
9406
|
+
}
|
|
9407
|
+
graph;
|
|
9408
|
+
hooks;
|
|
9409
|
+
options;
|
|
9410
|
+
async run() {
|
|
9411
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
9412
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
9413
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
9414
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
9415
|
+
for (const node of nodes) {
|
|
9416
|
+
for (const dependencyId of node.dependsOn) {
|
|
9417
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
9418
|
+
}
|
|
9419
|
+
}
|
|
9420
|
+
const completed = /* @__PURE__ */ new Set();
|
|
9421
|
+
const results = /* @__PURE__ */ new Map();
|
|
9422
|
+
const waves = [];
|
|
9423
|
+
let wave = 0;
|
|
9424
|
+
while (completed.size < nodes.length) {
|
|
9425
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
9426
|
+
if (ready.length === 0) {
|
|
9427
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
9428
|
+
}
|
|
9429
|
+
waves.push(ready.map((node) => node.id));
|
|
9430
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
9431
|
+
const runOne = async (node) => {
|
|
9432
|
+
const result = await this.hooks.execute(node);
|
|
9433
|
+
results.set(node.id, result);
|
|
9434
|
+
completed.add(node.id);
|
|
9435
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
9436
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9437
|
+
}
|
|
9438
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
9439
|
+
};
|
|
9440
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
9441
|
+
for (const node of ready) await runOne(node);
|
|
9442
|
+
} else {
|
|
9443
|
+
await Promise.all(ready.map(runOne));
|
|
9444
|
+
}
|
|
9445
|
+
wave++;
|
|
9446
|
+
}
|
|
9447
|
+
return { results, waves };
|
|
9448
|
+
}
|
|
9449
|
+
};
|
|
9450
|
+
|
|
9028
9451
|
// src/utils/json-extract.ts
|
|
9029
9452
|
function extractFirstJsonObject(input) {
|
|
9030
9453
|
if (!input) return null;
|
|
@@ -9096,7 +9519,7 @@ function buildT1SystemPrompt(has) {
|
|
|
9096
9519
|
Your responsibilities:
|
|
9097
9520
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
9098
9521
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
9099
|
-
3. For each section, define
|
|
9522
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
9100
9523
|
4. Return a structured plan as JSON
|
|
9101
9524
|
|
|
9102
9525
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -9511,7 +9934,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9511
9934
|
}
|
|
9512
9935
|
}
|
|
9513
9936
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
9514
|
-
if (plan.sections.length < min)
|
|
9937
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
9938
|
+
this.log(
|
|
9939
|
+
`\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.`
|
|
9940
|
+
);
|
|
9941
|
+
}
|
|
9515
9942
|
}
|
|
9516
9943
|
async dispatchT2Managers(sections) {
|
|
9517
9944
|
for (const section of sections) {
|
|
@@ -9623,114 +10050,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9623
10050
|
return t2Results;
|
|
9624
10051
|
}
|
|
9625
10052
|
/**
|
|
9626
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
10053
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
10054
|
+
*
|
|
10055
|
+
* Graph construction, validation and cycle repair now live in
|
|
10056
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
10057
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
10058
|
+
* ordered by one implementation rather than two that drifted.
|
|
10059
|
+
*
|
|
10060
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
10061
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
10062
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
10063
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
10064
|
+
* sections and started them immediately — before the sections they consume.
|
|
10065
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
10066
|
+
* members are touched.
|
|
9627
10067
|
*/
|
|
9628
10068
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
9629
|
-
const
|
|
9630
|
-
const
|
|
9631
|
-
|
|
9632
|
-
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
9633
|
-
for (const s of sections) {
|
|
9634
|
-
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
9635
|
-
inDegree.set(s.sectionId, 0);
|
|
9636
|
-
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
9637
|
-
}
|
|
9638
|
-
for (const s of sections) {
|
|
9639
|
-
for (const dep of s.dependsOn ?? []) {
|
|
9640
|
-
adj.get(dep).add(s.sectionId);
|
|
9641
|
-
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
9642
|
-
}
|
|
10069
|
+
const compiled = compileSectionGraph(sections);
|
|
10070
|
+
for (const issue of compiled.issues) {
|
|
10071
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
9643
10072
|
}
|
|
9644
|
-
const
|
|
9645
|
-
const
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
while (queue.length > 0) {
|
|
9649
|
-
const u = queue.shift();
|
|
9650
|
-
visited.add(u);
|
|
9651
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
9652
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
9653
|
-
degree.set(v, newDeg);
|
|
9654
|
-
if (newDeg === 0) queue.push(v);
|
|
9655
|
-
}
|
|
9656
|
-
}
|
|
9657
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
9658
|
-
if (cycleNodes.length > 0) {
|
|
9659
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
9660
|
-
for (const s of sections) {
|
|
9661
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
9662
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
9663
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
9664
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
9665
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
9666
|
-
}
|
|
9667
|
-
s.dependsOn = safeDeps;
|
|
9668
|
-
}
|
|
9669
|
-
}
|
|
10073
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
10074
|
+
for (const section of sections) {
|
|
10075
|
+
const node = byId.get(section.sectionId);
|
|
10076
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
9670
10077
|
}
|
|
9671
|
-
const
|
|
10078
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
10079
|
+
const totalSections = compiled.graph.nodes.length;
|
|
9672
10080
|
let completedSections = 0;
|
|
9673
|
-
const
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
10081
|
+
const scheduler = new DependencyScheduler(
|
|
10082
|
+
compiled.graph,
|
|
10083
|
+
{
|
|
10084
|
+
execute: async (node) => {
|
|
10085
|
+
const section = node.payload;
|
|
10086
|
+
const manager = managers[node.ordinal];
|
|
10087
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
10088
|
+
this.sendStatusUpdate({
|
|
10089
|
+
progressPct,
|
|
10090
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
10091
|
+
status: "IN_PROGRESS"
|
|
10092
|
+
});
|
|
10093
|
+
this.throwIfCancelled();
|
|
10094
|
+
try {
|
|
10095
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
10096
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
10097
|
+
if (result.status === "ESCALATED") {
|
|
10098
|
+
this.escalations.push({
|
|
10099
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
10100
|
+
sectionId: section.sectionId,
|
|
10101
|
+
attempted: result.issues,
|
|
10102
|
+
blocker: result.issues.join("; "),
|
|
10103
|
+
needs: "Human review required"
|
|
10104
|
+
});
|
|
10105
|
+
}
|
|
10106
|
+
return result;
|
|
10107
|
+
} catch (err) {
|
|
10108
|
+
return {
|
|
9700
10109
|
sectionId: section.sectionId,
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
10110
|
+
sectionTitle: section.sectionTitle,
|
|
10111
|
+
status: "FAILED",
|
|
10112
|
+
t3Results: [],
|
|
10113
|
+
sectionSummary: "",
|
|
10114
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
10115
|
+
};
|
|
9705
10116
|
}
|
|
9706
|
-
}
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
resultMap.set(id, result);
|
|
9717
|
-
completedSections++;
|
|
9718
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
9719
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9720
|
-
}
|
|
9721
|
-
};
|
|
9722
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
9723
|
-
for (const id of readyIds) {
|
|
9724
|
-
await runOne(id);
|
|
10117
|
+
},
|
|
10118
|
+
onNodeComplete: (node, result) => {
|
|
10119
|
+
resultMap.set(node.id, result);
|
|
10120
|
+
completedSections++;
|
|
10121
|
+
this.emit("section:complete", {
|
|
10122
|
+
id: node.id,
|
|
10123
|
+
title: result.sectionTitle,
|
|
10124
|
+
status: result.status,
|
|
10125
|
+
output: result.sectionSummary
|
|
10126
|
+
});
|
|
9725
10127
|
}
|
|
9726
|
-
}
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
10128
|
+
},
|
|
10129
|
+
{
|
|
10130
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
10131
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
10132
|
+
// serialized T3 workers within a single section while every section's
|
|
10133
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
10134
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
9731
10135
|
}
|
|
9732
|
-
|
|
9733
|
-
await
|
|
10136
|
+
);
|
|
10137
|
+
await scheduler.run();
|
|
9734
10138
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
9735
10139
|
}
|
|
9736
10140
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -10350,6 +10754,705 @@ function buildMediaTools(deps, readFile) {
|
|
|
10350
10754
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
10351
10755
|
return tools;
|
|
10352
10756
|
}
|
|
10757
|
+
var WorkspaceSandboxError = class extends Error {
|
|
10758
|
+
constructor(attempted, workspaceRoot) {
|
|
10759
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10760
|
+
this.name = "WorkspaceSandboxError";
|
|
10761
|
+
}
|
|
10762
|
+
};
|
|
10763
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
10764
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
10765
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10766
|
+
}
|
|
10767
|
+
const root = path26__default.default.resolve(workspaceRoot);
|
|
10768
|
+
const abs = path26__default.default.isAbsolute(input) ? path26__default.default.resolve(input) : path26__default.default.resolve(root, input);
|
|
10769
|
+
const rel = path26__default.default.relative(root, abs);
|
|
10770
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26__default.default.isAbsolute(rel)) {
|
|
10771
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10772
|
+
}
|
|
10773
|
+
try {
|
|
10774
|
+
const real = fs25__default.default.realpathSync(abs);
|
|
10775
|
+
const realRel = path26__default.default.relative(root, real);
|
|
10776
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26__default.default.isAbsolute(realRel))) {
|
|
10777
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10778
|
+
}
|
|
10779
|
+
} catch (e) {
|
|
10780
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10781
|
+
}
|
|
10782
|
+
return abs;
|
|
10783
|
+
}
|
|
10784
|
+
|
|
10785
|
+
// src/core/documents/blocks.ts
|
|
10786
|
+
function fileExt(name) {
|
|
10787
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
10788
|
+
const dot = base.lastIndexOf(".");
|
|
10789
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
10790
|
+
}
|
|
10791
|
+
function parseDelimited(text, name = "") {
|
|
10792
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
10793
|
+
const rows = [];
|
|
10794
|
+
let row = [];
|
|
10795
|
+
let cell = "";
|
|
10796
|
+
let quoted = false;
|
|
10797
|
+
for (let i = 0; i < text.length; i++) {
|
|
10798
|
+
const ch = text[i];
|
|
10799
|
+
if (quoted) {
|
|
10800
|
+
if (ch === '"') {
|
|
10801
|
+
if (text[i + 1] === '"') {
|
|
10802
|
+
cell += '"';
|
|
10803
|
+
i++;
|
|
10804
|
+
} else quoted = false;
|
|
10805
|
+
} else cell += ch;
|
|
10806
|
+
} else if (ch === '"') {
|
|
10807
|
+
quoted = true;
|
|
10808
|
+
} else if (ch === delim) {
|
|
10809
|
+
row.push(cell);
|
|
10810
|
+
cell = "";
|
|
10811
|
+
} else if (ch === "\n") {
|
|
10812
|
+
row.push(cell);
|
|
10813
|
+
rows.push(row);
|
|
10814
|
+
row = [];
|
|
10815
|
+
cell = "";
|
|
10816
|
+
} else if (ch === "\r") ; else {
|
|
10817
|
+
cell += ch;
|
|
10818
|
+
}
|
|
10819
|
+
if (rows.length > 5e3) break;
|
|
10820
|
+
}
|
|
10821
|
+
if (cell !== "" || row.length) {
|
|
10822
|
+
row.push(cell);
|
|
10823
|
+
rows.push(row);
|
|
10824
|
+
}
|
|
10825
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
10826
|
+
}
|
|
10827
|
+
var CHART_ALIASES = {
|
|
10828
|
+
bar: "bar",
|
|
10829
|
+
column: "bar",
|
|
10830
|
+
bars: "bar",
|
|
10831
|
+
barchart: "bar",
|
|
10832
|
+
horizontalbar: "bar",
|
|
10833
|
+
line: "line",
|
|
10834
|
+
lines: "line",
|
|
10835
|
+
linechart: "line",
|
|
10836
|
+
pie: "pie",
|
|
10837
|
+
piechart: "pie",
|
|
10838
|
+
doughnut: "doughnut",
|
|
10839
|
+
donut: "doughnut",
|
|
10840
|
+
area: "area",
|
|
10841
|
+
areachart: "area",
|
|
10842
|
+
scatter: "scatter",
|
|
10843
|
+
xy: "scatter"
|
|
10844
|
+
};
|
|
10845
|
+
function chartKind(raw) {
|
|
10846
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
10847
|
+
}
|
|
10848
|
+
function toNumber(cell) {
|
|
10849
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
10850
|
+
const n = Number.parseFloat(cleaned);
|
|
10851
|
+
return Number.isFinite(n) ? n : 0;
|
|
10852
|
+
}
|
|
10853
|
+
function parseChartSpec(kind, body) {
|
|
10854
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
10855
|
+
let title;
|
|
10856
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
10857
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
10858
|
+
if (titleMatch) {
|
|
10859
|
+
title = titleMatch[1].trim();
|
|
10860
|
+
lines.shift();
|
|
10861
|
+
}
|
|
10862
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
10863
|
+
if (rows.length < 2) return null;
|
|
10864
|
+
const header = rows[0].map((c) => c.trim());
|
|
10865
|
+
if (header.length < 2) return null;
|
|
10866
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
10867
|
+
if (!dataRows.length) return null;
|
|
10868
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
10869
|
+
const series = header.slice(1).map((name, col) => ({
|
|
10870
|
+
name: name || `Series ${col + 1}`,
|
|
10871
|
+
values: dataRows.map((r) => toNumber((r[col + 1] ?? "").trim()))
|
|
10872
|
+
}));
|
|
10873
|
+
if (!series.length) return null;
|
|
10874
|
+
return {
|
|
10875
|
+
kind,
|
|
10876
|
+
...title ? { title } : {},
|
|
10877
|
+
categoryName: header[0] || "Category",
|
|
10878
|
+
categories,
|
|
10879
|
+
series
|
|
10880
|
+
};
|
|
10881
|
+
}
|
|
10882
|
+
function chartToTableRows(spec) {
|
|
10883
|
+
return [
|
|
10884
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10885
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
10886
|
+
];
|
|
10887
|
+
}
|
|
10888
|
+
function parseBlocks(md) {
|
|
10889
|
+
const out = [];
|
|
10890
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
10891
|
+
let i = 0;
|
|
10892
|
+
let ordinal = 0;
|
|
10893
|
+
const reset = () => {
|
|
10894
|
+
ordinal = 0;
|
|
10895
|
+
};
|
|
10896
|
+
while (i < lines.length) {
|
|
10897
|
+
const line = lines[i] ?? "";
|
|
10898
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
10899
|
+
if (fence) {
|
|
10900
|
+
const info = (fence[1] ?? "").trim();
|
|
10901
|
+
i++;
|
|
10902
|
+
const code = [];
|
|
10903
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
10904
|
+
code.push(lines[i] ?? "");
|
|
10905
|
+
i++;
|
|
10906
|
+
}
|
|
10907
|
+
i++;
|
|
10908
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
10909
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
10910
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
10911
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
10912
|
+
reset();
|
|
10913
|
+
continue;
|
|
10914
|
+
}
|
|
10915
|
+
if (!line.trim()) {
|
|
10916
|
+
i++;
|
|
10917
|
+
reset();
|
|
10918
|
+
continue;
|
|
10919
|
+
}
|
|
10920
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
10921
|
+
if (h) {
|
|
10922
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
10923
|
+
i++;
|
|
10924
|
+
reset();
|
|
10925
|
+
continue;
|
|
10926
|
+
}
|
|
10927
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
10928
|
+
out.push({ t: "hr" });
|
|
10929
|
+
i++;
|
|
10930
|
+
reset();
|
|
10931
|
+
continue;
|
|
10932
|
+
}
|
|
10933
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
10934
|
+
if (li) {
|
|
10935
|
+
const ordered = /\d/.test(li[2]);
|
|
10936
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
10937
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
10938
|
+
i++;
|
|
10939
|
+
continue;
|
|
10940
|
+
}
|
|
10941
|
+
if (/^>\s?/.test(line)) {
|
|
10942
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
10943
|
+
i++;
|
|
10944
|
+
reset();
|
|
10945
|
+
continue;
|
|
10946
|
+
}
|
|
10947
|
+
if (/^\|.*\|/.test(line)) {
|
|
10948
|
+
const rows = [];
|
|
10949
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
10950
|
+
const r = lines[i];
|
|
10951
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
10952
|
+
i++;
|
|
10953
|
+
}
|
|
10954
|
+
out.push({ t: "table", rows });
|
|
10955
|
+
reset();
|
|
10956
|
+
continue;
|
|
10957
|
+
}
|
|
10958
|
+
out.push({ t: "para", text: line });
|
|
10959
|
+
i++;
|
|
10960
|
+
reset();
|
|
10961
|
+
}
|
|
10962
|
+
return out;
|
|
10963
|
+
}
|
|
10964
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
10965
|
+
function matchImageLine(s) {
|
|
10966
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
10967
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
10968
|
+
}
|
|
10969
|
+
function sniffImage(b) {
|
|
10970
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
10971
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
10972
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
10973
|
+
}
|
|
10974
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
10975
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
10976
|
+
}
|
|
10977
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
10978
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
10979
|
+
}
|
|
10980
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
10981
|
+
let i = 2;
|
|
10982
|
+
while (i + 9 < b.length) {
|
|
10983
|
+
if (b[i] !== 255) {
|
|
10984
|
+
i++;
|
|
10985
|
+
continue;
|
|
10986
|
+
}
|
|
10987
|
+
const marker = b[i + 1];
|
|
10988
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
10989
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
10990
|
+
}
|
|
10991
|
+
i += 2 + dv.getUint16(i + 2);
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
return null;
|
|
10995
|
+
}
|
|
10996
|
+
function bytesToBase64(bytes) {
|
|
10997
|
+
const g = globalThis;
|
|
10998
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
10999
|
+
const CHUNK = 32768;
|
|
11000
|
+
let binary = "";
|
|
11001
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
11002
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
11003
|
+
}
|
|
11004
|
+
return g.btoa(binary);
|
|
11005
|
+
}
|
|
11006
|
+
function stripInline(s) {
|
|
11007
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
11008
|
+
}
|
|
11009
|
+
function inlineRuns(text) {
|
|
11010
|
+
const acc = [];
|
|
11011
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
11012
|
+
let last = 0;
|
|
11013
|
+
let m;
|
|
11014
|
+
while ((m = re.exec(text)) !== null) {
|
|
11015
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
11016
|
+
const tok = m[0];
|
|
11017
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
11018
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
11019
|
+
else if (tok.startsWith("[")) {
|
|
11020
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
11021
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
11022
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
11023
|
+
last = re.lastIndex;
|
|
11024
|
+
}
|
|
11025
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
11026
|
+
return acc.length ? acc : [{ text }];
|
|
11027
|
+
}
|
|
11028
|
+
function splitSlides(md) {
|
|
11029
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
11030
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
11031
|
+
if (chunks.length === 1) {
|
|
11032
|
+
const lines = norm.split("\n");
|
|
11033
|
+
const sections = [];
|
|
11034
|
+
let cur = [];
|
|
11035
|
+
for (const ln of lines) {
|
|
11036
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
11037
|
+
sections.push(cur.join("\n"));
|
|
11038
|
+
cur = [];
|
|
11039
|
+
}
|
|
11040
|
+
cur.push(ln);
|
|
11041
|
+
}
|
|
11042
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
11043
|
+
if (sections.length > 1) chunks = sections;
|
|
11044
|
+
}
|
|
11045
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
11046
|
+
}
|
|
11047
|
+
function parseSlide(chunk) {
|
|
11048
|
+
let title = "";
|
|
11049
|
+
const body = [];
|
|
11050
|
+
const images = [];
|
|
11051
|
+
const charts = [];
|
|
11052
|
+
const lines = chunk.split("\n");
|
|
11053
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11054
|
+
const raw = lines[i] ?? "";
|
|
11055
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
11056
|
+
if (fence) {
|
|
11057
|
+
const kind = chartKind(fence[1]);
|
|
11058
|
+
const bodyLines = [];
|
|
11059
|
+
i++;
|
|
11060
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
11061
|
+
bodyLines.push(lines[i] ?? "");
|
|
11062
|
+
i++;
|
|
11063
|
+
}
|
|
11064
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
11065
|
+
if (spec) charts.push(spec);
|
|
11066
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
11067
|
+
continue;
|
|
11068
|
+
}
|
|
11069
|
+
const line = raw.trim();
|
|
11070
|
+
if (!line) continue;
|
|
11071
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
11072
|
+
if (h) {
|
|
11073
|
+
if (!title) title = stripInline(h[1]);
|
|
11074
|
+
else body.push(stripInline(h[1]));
|
|
11075
|
+
continue;
|
|
11076
|
+
}
|
|
11077
|
+
const img = matchImageLine(line);
|
|
11078
|
+
if (img) {
|
|
11079
|
+
images.push(img);
|
|
11080
|
+
continue;
|
|
11081
|
+
}
|
|
11082
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
11083
|
+
if (li) {
|
|
11084
|
+
const bulletImg = matchImageLine(li[2]);
|
|
11085
|
+
if (bulletImg) {
|
|
11086
|
+
images.push(bulletImg);
|
|
11087
|
+
continue;
|
|
11088
|
+
}
|
|
11089
|
+
body.push(stripInline(li[2]));
|
|
11090
|
+
continue;
|
|
11091
|
+
}
|
|
11092
|
+
if (/^>\s?/.test(line)) {
|
|
11093
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
11094
|
+
continue;
|
|
11095
|
+
}
|
|
11096
|
+
body.push(stripInline(line));
|
|
11097
|
+
}
|
|
11098
|
+
return { title, body, images, charts };
|
|
11099
|
+
}
|
|
11100
|
+
function extractCharts(source) {
|
|
11101
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
11102
|
+
const charts = [];
|
|
11103
|
+
const rest = [];
|
|
11104
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11105
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
11106
|
+
if (!fence) {
|
|
11107
|
+
rest.push(lines[i]);
|
|
11108
|
+
continue;
|
|
11109
|
+
}
|
|
11110
|
+
const kind = chartKind(fence[1]);
|
|
11111
|
+
const body = [];
|
|
11112
|
+
i++;
|
|
11113
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
11114
|
+
body.push(lines[i] ?? "");
|
|
11115
|
+
i++;
|
|
11116
|
+
}
|
|
11117
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
11118
|
+
if (spec) charts.push(spec);
|
|
11119
|
+
else rest.push(...body);
|
|
11120
|
+
}
|
|
11121
|
+
return { charts, rest: rest.join("\n") };
|
|
11122
|
+
}
|
|
11123
|
+
|
|
11124
|
+
// src/core/documents/render.ts
|
|
11125
|
+
var DOCUMENT_MIME = {
|
|
11126
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
11127
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
11128
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
11129
|
+
};
|
|
11130
|
+
function isDocumentFormat(ext) {
|
|
11131
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
11132
|
+
}
|
|
11133
|
+
async function loadImage(ref, load) {
|
|
11134
|
+
if (!load) return null;
|
|
11135
|
+
try {
|
|
11136
|
+
const bytes = await load(ref.url);
|
|
11137
|
+
if (!bytes || !bytes.length) return null;
|
|
11138
|
+
const info = sniffImage(bytes);
|
|
11139
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
11140
|
+
return { ...info, bytes, alt: ref.alt };
|
|
11141
|
+
} catch {
|
|
11142
|
+
return null;
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
async function renderXlsx(source, opts = {}) {
|
|
11146
|
+
const XLSX = await import('xlsx');
|
|
11147
|
+
const { charts, rest } = extractCharts(source);
|
|
11148
|
+
const name = opts.name ?? "";
|
|
11149
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
11150
|
+
const wb = XLSX.utils.book_new();
|
|
11151
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
11152
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
11153
|
+
charts.forEach((spec, i) => {
|
|
11154
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
11155
|
+
let sheetName = base;
|
|
11156
|
+
let n = 2;
|
|
11157
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
11158
|
+
used.add(sheetName);
|
|
11159
|
+
const aoa = [
|
|
11160
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
11161
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
11162
|
+
];
|
|
11163
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
11164
|
+
});
|
|
11165
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
11166
|
+
}
|
|
11167
|
+
var DOCX_MAX_W = 600;
|
|
11168
|
+
var DOCX_MAX_H = 864;
|
|
11169
|
+
async function renderDocx(md, opts = {}) {
|
|
11170
|
+
const d = await import('docx');
|
|
11171
|
+
const {
|
|
11172
|
+
Document,
|
|
11173
|
+
Packer,
|
|
11174
|
+
Paragraph,
|
|
11175
|
+
TextRun,
|
|
11176
|
+
ImageRun,
|
|
11177
|
+
HeadingLevel,
|
|
11178
|
+
Table,
|
|
11179
|
+
TableRow,
|
|
11180
|
+
TableCell,
|
|
11181
|
+
WidthType,
|
|
11182
|
+
BorderStyle,
|
|
11183
|
+
AlignmentType
|
|
11184
|
+
} = d;
|
|
11185
|
+
const HEADINGS = [
|
|
11186
|
+
HeadingLevel.HEADING_1,
|
|
11187
|
+
HeadingLevel.HEADING_2,
|
|
11188
|
+
HeadingLevel.HEADING_3,
|
|
11189
|
+
HeadingLevel.HEADING_4,
|
|
11190
|
+
HeadingLevel.HEADING_5,
|
|
11191
|
+
HeadingLevel.HEADING_6
|
|
11192
|
+
];
|
|
11193
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
11194
|
+
text: r.text,
|
|
11195
|
+
...r.bold ? { bold: true } : {},
|
|
11196
|
+
...r.italic ? { italics: true } : {},
|
|
11197
|
+
...r.code ? { font: "Courier New" } : {}
|
|
11198
|
+
}));
|
|
11199
|
+
const tableOf = (rows) => new Table({
|
|
11200
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
11201
|
+
rows: rows.map((r) => new TableRow({
|
|
11202
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
11203
|
+
}))
|
|
11204
|
+
});
|
|
11205
|
+
const imageParagraph = (img) => {
|
|
11206
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
11207
|
+
return new Paragraph({
|
|
11208
|
+
children: [new ImageRun({
|
|
11209
|
+
type: img.type,
|
|
11210
|
+
data: img.bytes,
|
|
11211
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
11212
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
11213
|
+
})]
|
|
11214
|
+
});
|
|
11215
|
+
};
|
|
11216
|
+
const asImage = async (text) => {
|
|
11217
|
+
const ref = matchImageLine(text);
|
|
11218
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
11219
|
+
};
|
|
11220
|
+
const children = [];
|
|
11221
|
+
for (const b of parseBlocks(md)) {
|
|
11222
|
+
switch (b.t) {
|
|
11223
|
+
case "heading":
|
|
11224
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
11225
|
+
break;
|
|
11226
|
+
case "para": {
|
|
11227
|
+
const img = await asImage(b.text);
|
|
11228
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
11229
|
+
break;
|
|
11230
|
+
}
|
|
11231
|
+
case "bullet": {
|
|
11232
|
+
const img = await asImage(b.text);
|
|
11233
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
11234
|
+
children: runs(b.text),
|
|
11235
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
11236
|
+
}));
|
|
11237
|
+
break;
|
|
11238
|
+
}
|
|
11239
|
+
case "code":
|
|
11240
|
+
for (const cl of b.lines) {
|
|
11241
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
11242
|
+
}
|
|
11243
|
+
break;
|
|
11244
|
+
case "quote":
|
|
11245
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
11246
|
+
break;
|
|
11247
|
+
case "table":
|
|
11248
|
+
children.push(tableOf(b.rows));
|
|
11249
|
+
break;
|
|
11250
|
+
case "chart":
|
|
11251
|
+
if (b.spec.title) {
|
|
11252
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
11253
|
+
}
|
|
11254
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
11255
|
+
break;
|
|
11256
|
+
case "hr":
|
|
11257
|
+
children.push(new Paragraph({
|
|
11258
|
+
children: [],
|
|
11259
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
11260
|
+
}));
|
|
11261
|
+
break;
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
const doc = new Document({
|
|
11265
|
+
numbering: {
|
|
11266
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
11267
|
+
},
|
|
11268
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
11269
|
+
});
|
|
11270
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
11271
|
+
}
|
|
11272
|
+
function pptxConstructor(mod) {
|
|
11273
|
+
let c = mod;
|
|
11274
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
11275
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
11276
|
+
return c;
|
|
11277
|
+
}
|
|
11278
|
+
var SLIDE_H = 7.5;
|
|
11279
|
+
var CONTENT_X = 0.6;
|
|
11280
|
+
var CONTENT_W = 12.1;
|
|
11281
|
+
var CONTENT_BOTTOM = 0.4;
|
|
11282
|
+
async function renderPptx(md, opts = {}) {
|
|
11283
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
11284
|
+
const pptx = new PptxGen();
|
|
11285
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
11286
|
+
const CHART_TYPES = {
|
|
11287
|
+
bar: pptx.ChartType["bar"],
|
|
11288
|
+
line: pptx.ChartType["line"],
|
|
11289
|
+
pie: pptx.ChartType["pie"],
|
|
11290
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
11291
|
+
area: pptx.ChartType["area"],
|
|
11292
|
+
scatter: pptx.ChartType["scatter"]
|
|
11293
|
+
};
|
|
11294
|
+
const slides = splitSlides(md);
|
|
11295
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
11296
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
11297
|
+
const slide = pptx.addSlide();
|
|
11298
|
+
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" });
|
|
11299
|
+
const top = s.title ? 1.5 : 0.5;
|
|
11300
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
11301
|
+
const visuals = [
|
|
11302
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
11303
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
11304
|
+
];
|
|
11305
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
11306
|
+
if (s.body.length) {
|
|
11307
|
+
slide.addText(
|
|
11308
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
11309
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
11310
|
+
);
|
|
11311
|
+
}
|
|
11312
|
+
if (visuals.length) {
|
|
11313
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
11314
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
11315
|
+
const gap = 0.3;
|
|
11316
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
11317
|
+
visuals.forEach((v, i) => {
|
|
11318
|
+
const x = CONTENT_X + i * (w + gap);
|
|
11319
|
+
if (v.kind === "image") {
|
|
11320
|
+
slide.addImage({
|
|
11321
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
11322
|
+
x,
|
|
11323
|
+
y,
|
|
11324
|
+
w,
|
|
11325
|
+
h,
|
|
11326
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
11327
|
+
// render looks worse than a smaller correct one.
|
|
11328
|
+
sizing: { type: "contain", w, h },
|
|
11329
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
11330
|
+
});
|
|
11331
|
+
} else {
|
|
11332
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
11333
|
+
x,
|
|
11334
|
+
y,
|
|
11335
|
+
w,
|
|
11336
|
+
h,
|
|
11337
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
11338
|
+
legendPos: "b",
|
|
11339
|
+
showTitle: !!v.spec.title,
|
|
11340
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
11341
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
11342
|
+
});
|
|
11343
|
+
}
|
|
11344
|
+
});
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
11348
|
+
}
|
|
11349
|
+
function chartData(spec) {
|
|
11350
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
11351
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
11352
|
+
}
|
|
11353
|
+
async function renderDocument(format, source, opts = {}) {
|
|
11354
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
11355
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
11356
|
+
return renderXlsx(source, opts);
|
|
11357
|
+
}
|
|
11358
|
+
|
|
11359
|
+
// src/tools/generate-document.ts
|
|
11360
|
+
var SOURCE_HINT = {
|
|
11361
|
+
docx: "Markdown",
|
|
11362
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
11363
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
11364
|
+
};
|
|
11365
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
11366
|
+
/**
|
|
11367
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
11368
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
11369
|
+
*/
|
|
11370
|
+
constructor(readFile = async (p) => new Uint8Array(await fs5__default.default.readFile(p))) {
|
|
11371
|
+
super();
|
|
11372
|
+
this.readFile = readFile;
|
|
11373
|
+
}
|
|
11374
|
+
readFile;
|
|
11375
|
+
name = "generate_document";
|
|
11376
|
+
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.";
|
|
11377
|
+
inputSchema = {
|
|
11378
|
+
type: "object",
|
|
11379
|
+
properties: {
|
|
11380
|
+
path: {
|
|
11381
|
+
type: "string",
|
|
11382
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
11383
|
+
},
|
|
11384
|
+
content: {
|
|
11385
|
+
type: "string",
|
|
11386
|
+
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.'
|
|
11387
|
+
},
|
|
11388
|
+
format: {
|
|
11389
|
+
type: "string",
|
|
11390
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
11391
|
+
}
|
|
11392
|
+
},
|
|
11393
|
+
required: ["path", "content"]
|
|
11394
|
+
};
|
|
11395
|
+
isDangerous() {
|
|
11396
|
+
return true;
|
|
11397
|
+
}
|
|
11398
|
+
async execute(input, options) {
|
|
11399
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11400
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
11401
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
11402
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
11403
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
11404
|
+
const ext = requested || fileExt(filePath);
|
|
11405
|
+
if (!isDocumentFormat(ext)) {
|
|
11406
|
+
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.`;
|
|
11407
|
+
}
|
|
11408
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
11409
|
+
const embedded = [];
|
|
11410
|
+
const skipped = [];
|
|
11411
|
+
const loadImageBytes = async (url) => {
|
|
11412
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
11413
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
11414
|
+
return null;
|
|
11415
|
+
}
|
|
11416
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
11417
|
+
try {
|
|
11418
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
11419
|
+
embedded.push(local);
|
|
11420
|
+
return bytes2;
|
|
11421
|
+
} catch {
|
|
11422
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
11423
|
+
return null;
|
|
11424
|
+
}
|
|
11425
|
+
};
|
|
11426
|
+
if (options.saveSnapshot) {
|
|
11427
|
+
try {
|
|
11428
|
+
const old = await fs5__default.default.readFile(absPath, "utf-8");
|
|
11429
|
+
await options.saveSnapshot(absPath, old);
|
|
11430
|
+
} catch {
|
|
11431
|
+
}
|
|
11432
|
+
}
|
|
11433
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11434
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
11435
|
+
await fs5__default.default.writeFile(absPath, bytes);
|
|
11436
|
+
const charts = countCharts(content);
|
|
11437
|
+
return [
|
|
11438
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
11439
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
11440
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
11441
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
11442
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
11443
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
11444
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
11445
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
11446
|
+
].filter(Boolean).join("\n");
|
|
11447
|
+
}
|
|
11448
|
+
};
|
|
11449
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
11450
|
+
function countCharts(source) {
|
|
11451
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
11452
|
+
}
|
|
11453
|
+
function buildDocumentTools(readFile) {
|
|
11454
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
11455
|
+
}
|
|
10353
11456
|
|
|
10354
11457
|
// src/core/run-breaker.ts
|
|
10355
11458
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -10672,35 +11775,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
10672
11775
|
}
|
|
10673
11776
|
}
|
|
10674
11777
|
};
|
|
10675
|
-
var WorkspaceSandboxError = class extends Error {
|
|
10676
|
-
constructor(attempted, workspaceRoot) {
|
|
10677
|
-
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10678
|
-
this.name = "WorkspaceSandboxError";
|
|
10679
|
-
}
|
|
10680
|
-
};
|
|
10681
|
-
function resolveInWorkspace(workspaceRoot, input) {
|
|
10682
|
-
if (typeof input !== "string" || input.length === 0) {
|
|
10683
|
-
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10684
|
-
}
|
|
10685
|
-
const root = path24__default.default.resolve(workspaceRoot);
|
|
10686
|
-
const abs = path24__default.default.isAbsolute(input) ? path24__default.default.resolve(input) : path24__default.default.resolve(root, input);
|
|
10687
|
-
const rel = path24__default.default.relative(root, abs);
|
|
10688
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24__default.default.isAbsolute(rel)) {
|
|
10689
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10690
|
-
}
|
|
10691
|
-
try {
|
|
10692
|
-
const real = fs23__default.default.realpathSync(abs);
|
|
10693
|
-
const realRel = path24__default.default.relative(root, real);
|
|
10694
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24__default.default.isAbsolute(realRel))) {
|
|
10695
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10696
|
-
}
|
|
10697
|
-
} catch (e) {
|
|
10698
|
-
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10699
|
-
}
|
|
10700
|
-
return abs;
|
|
10701
|
-
}
|
|
10702
|
-
|
|
10703
|
-
// src/tools/file.ts
|
|
10704
11778
|
var FileReadTool = class extends BaseTool {
|
|
10705
11779
|
name = "file_read";
|
|
10706
11780
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -10718,7 +11792,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
10718
11792
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10719
11793
|
const offset = input["offset"] ?? 1;
|
|
10720
11794
|
const limit = input["limit"];
|
|
10721
|
-
const content = await
|
|
11795
|
+
const content = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10722
11796
|
const lines = content.split("\n");
|
|
10723
11797
|
const start = Math.max(0, offset - 1);
|
|
10724
11798
|
const end = limit ? start + limit : lines.length;
|
|
@@ -10747,13 +11821,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
10747
11821
|
const content = input["content"];
|
|
10748
11822
|
if (options.saveSnapshot) {
|
|
10749
11823
|
try {
|
|
10750
|
-
const oldContent = await
|
|
11824
|
+
const oldContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10751
11825
|
await options.saveSnapshot(absPath, oldContent);
|
|
10752
11826
|
} catch {
|
|
10753
11827
|
}
|
|
10754
11828
|
}
|
|
10755
|
-
await
|
|
10756
|
-
await
|
|
11829
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(absPath), { recursive: true });
|
|
11830
|
+
await fs5__default.default.writeFile(absPath, content, "utf-8");
|
|
10757
11831
|
return `Written ${content.length} characters to ${filePath}`;
|
|
10758
11832
|
}
|
|
10759
11833
|
};
|
|
@@ -10779,7 +11853,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10779
11853
|
const oldString = input["old_string"];
|
|
10780
11854
|
const newString = input["new_string"];
|
|
10781
11855
|
const replaceAll = input["replace_all"] ?? false;
|
|
10782
|
-
const rawContent = await
|
|
11856
|
+
const rawContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10783
11857
|
if (options.saveSnapshot) {
|
|
10784
11858
|
await options.saveSnapshot(absPath, rawContent);
|
|
10785
11859
|
}
|
|
@@ -10791,7 +11865,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10791
11865
|
);
|
|
10792
11866
|
}
|
|
10793
11867
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
10794
|
-
await
|
|
11868
|
+
await fs5__default.default.writeFile(absPath, updated, "utf-8");
|
|
10795
11869
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
10796
11870
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
10797
11871
|
}
|
|
@@ -10814,12 +11888,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
10814
11888
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10815
11889
|
if (options.saveSnapshot) {
|
|
10816
11890
|
try {
|
|
10817
|
-
const oldContent = await
|
|
11891
|
+
const oldContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10818
11892
|
await options.saveSnapshot(absPath, oldContent);
|
|
10819
11893
|
} catch {
|
|
10820
11894
|
}
|
|
10821
11895
|
}
|
|
10822
|
-
await
|
|
11896
|
+
await fs5__default.default.rm(absPath, { recursive: false });
|
|
10823
11897
|
return `Deleted ${filePath}`;
|
|
10824
11898
|
}
|
|
10825
11899
|
};
|
|
@@ -10836,7 +11910,7 @@ var FileListTool = class extends BaseTool {
|
|
|
10836
11910
|
async execute(input, _options) {
|
|
10837
11911
|
const inputPath = input["path"] || ".";
|
|
10838
11912
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
10839
|
-
const entries = await
|
|
11913
|
+
const entries = await fs5__default.default.readdir(absPath, { withFileTypes: true });
|
|
10840
11914
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
10841
11915
|
}
|
|
10842
11916
|
};
|
|
@@ -11249,8 +12323,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11249
12323
|
}
|
|
11250
12324
|
};
|
|
11251
12325
|
async function fileToImageAttachment(filePath) {
|
|
11252
|
-
const data = await
|
|
11253
|
-
const ext =
|
|
12326
|
+
const data = await fs5__default.default.readFile(filePath);
|
|
12327
|
+
const ext = path26__default.default.extname(filePath).toLowerCase();
|
|
11254
12328
|
const mimeMap = {
|
|
11255
12329
|
".jpg": "image/jpeg",
|
|
11256
12330
|
".jpeg": "image/jpeg",
|
|
@@ -11284,14 +12358,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11284
12358
|
const filePath = input["path"];
|
|
11285
12359
|
const content = input["content"];
|
|
11286
12360
|
const title = input["title"];
|
|
11287
|
-
const dir =
|
|
11288
|
-
if (!
|
|
11289
|
-
|
|
12361
|
+
const dir = path26__default.default.dirname(filePath);
|
|
12362
|
+
if (!fs25__default.default.existsSync(dir)) {
|
|
12363
|
+
fs25__default.default.mkdirSync(dir, { recursive: true });
|
|
11290
12364
|
}
|
|
11291
12365
|
return new Promise((resolve2, reject) => {
|
|
11292
12366
|
try {
|
|
11293
12367
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
11294
|
-
const stream =
|
|
12368
|
+
const stream = fs25__default.default.createWriteStream(filePath);
|
|
11295
12369
|
doc.pipe(stream);
|
|
11296
12370
|
if (title) {
|
|
11297
12371
|
doc.info["Title"] = title;
|
|
@@ -11369,22 +12443,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11369
12443
|
}
|
|
11370
12444
|
cmdPrefix = NODE_CMD;
|
|
11371
12445
|
}
|
|
11372
|
-
const tmpDir =
|
|
11373
|
-
if (!
|
|
11374
|
-
|
|
12446
|
+
const tmpDir = path26__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12447
|
+
if (!fs25__default.default.existsSync(tmpDir)) {
|
|
12448
|
+
fs25__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
11375
12449
|
}
|
|
11376
12450
|
const extension = language === "python" ? "py" : "js";
|
|
11377
12451
|
const fileName = `intp_${crypto4.randomUUID().slice(0, 8)}.${extension}`;
|
|
11378
|
-
const filePath =
|
|
11379
|
-
|
|
12452
|
+
const filePath = path26__default.default.join(tmpDir, fileName);
|
|
12453
|
+
fs25__default.default.writeFileSync(filePath, code, "utf-8");
|
|
11380
12454
|
const execArgs = [filePath, ...args];
|
|
11381
12455
|
return new Promise((resolve2) => {
|
|
11382
12456
|
const startMs = Date.now();
|
|
11383
12457
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11384
12458
|
const duration = Date.now() - startMs;
|
|
11385
12459
|
try {
|
|
11386
|
-
if (
|
|
11387
|
-
|
|
12460
|
+
if (fs25__default.default.existsSync(filePath)) {
|
|
12461
|
+
fs25__default.default.unlinkSync(filePath);
|
|
11388
12462
|
}
|
|
11389
12463
|
} catch (cleanupErr) {
|
|
11390
12464
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -11696,7 +12770,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11696
12770
|
};
|
|
11697
12771
|
async execute(input, _options) {
|
|
11698
12772
|
const pattern = input["pattern"];
|
|
11699
|
-
const searchPath = input["path"] ?
|
|
12773
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11700
12774
|
const matches = await glob.glob(pattern, {
|
|
11701
12775
|
cwd: searchPath,
|
|
11702
12776
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -11709,7 +12783,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11709
12783
|
const withMtime = await Promise.all(
|
|
11710
12784
|
matches.map(async (rel) => {
|
|
11711
12785
|
try {
|
|
11712
|
-
const stat = await
|
|
12786
|
+
const stat = await fs5__default.default.stat(path26__default.default.join(searchPath, rel));
|
|
11713
12787
|
return { rel, mtime: stat.mtimeMs };
|
|
11714
12788
|
} catch {
|
|
11715
12789
|
return { rel, mtime: 0 };
|
|
@@ -11758,7 +12832,7 @@ var GrepTool = class extends BaseTool {
|
|
|
11758
12832
|
};
|
|
11759
12833
|
async execute(input, _options) {
|
|
11760
12834
|
const pattern = input["pattern"];
|
|
11761
|
-
const searchPath = input["path"] ?
|
|
12835
|
+
const searchPath = input["path"] ? path26__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11762
12836
|
const globPattern = input["glob"];
|
|
11763
12837
|
const outputMode = input["output_mode"] ?? "content";
|
|
11764
12838
|
const context = input["context"] ?? 0;
|
|
@@ -11812,15 +12886,15 @@ var GrepTool = class extends BaseTool {
|
|
|
11812
12886
|
nodir: true
|
|
11813
12887
|
});
|
|
11814
12888
|
} catch {
|
|
11815
|
-
files = [
|
|
12889
|
+
files = [path26__default.default.relative(searchPath, searchPath) || "."];
|
|
11816
12890
|
}
|
|
11817
12891
|
const results = [];
|
|
11818
12892
|
let totalCount = 0;
|
|
11819
12893
|
for (const rel of files) {
|
|
11820
|
-
const abs =
|
|
12894
|
+
const abs = path26__default.default.join(searchPath, rel);
|
|
11821
12895
|
let content;
|
|
11822
12896
|
try {
|
|
11823
|
-
content = await
|
|
12897
|
+
content = await fs5__default.default.readFile(abs, "utf-8");
|
|
11824
12898
|
} catch {
|
|
11825
12899
|
continue;
|
|
11826
12900
|
}
|
|
@@ -12204,10 +13278,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
12204
13278
|
}
|
|
12205
13279
|
isIgnored(filePath) {
|
|
12206
13280
|
if (!filePath) return false;
|
|
12207
|
-
const abs =
|
|
12208
|
-
const rel =
|
|
12209
|
-
if (!rel || rel.startsWith("..") ||
|
|
12210
|
-
const posixRel = rel.split(
|
|
13281
|
+
const abs = path26__default.default.resolve(this.workspaceRoot, filePath);
|
|
13282
|
+
const rel = path26__default.default.relative(this.workspaceRoot, abs);
|
|
13283
|
+
if (!rel || rel.startsWith("..") || path26__default.default.isAbsolute(rel)) return true;
|
|
13284
|
+
const posixRel = rel.split(path26__default.default.sep).join("/");
|
|
12211
13285
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12212
13286
|
}
|
|
12213
13287
|
};
|
|
@@ -12441,22 +13515,22 @@ var FileMcpOAuthStore = class {
|
|
|
12441
13515
|
filePath;
|
|
12442
13516
|
load() {
|
|
12443
13517
|
try {
|
|
12444
|
-
return JSON.parse(
|
|
13518
|
+
return JSON.parse(fs25__default.default.readFileSync(this.filePath, "utf-8"));
|
|
12445
13519
|
} catch {
|
|
12446
13520
|
return void 0;
|
|
12447
13521
|
}
|
|
12448
13522
|
}
|
|
12449
13523
|
save(state) {
|
|
12450
|
-
|
|
12451
|
-
|
|
13524
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13525
|
+
fs25__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
12452
13526
|
try {
|
|
12453
|
-
|
|
13527
|
+
fs25__default.default.chmodSync(this.filePath, 384);
|
|
12454
13528
|
} catch {
|
|
12455
13529
|
}
|
|
12456
13530
|
}
|
|
12457
13531
|
clear() {
|
|
12458
13532
|
try {
|
|
12459
|
-
|
|
13533
|
+
fs25__default.default.rmSync(this.filePath);
|
|
12460
13534
|
} catch {
|
|
12461
13535
|
}
|
|
12462
13536
|
}
|
|
@@ -13353,6 +14427,14 @@ function heuristicAnalyze(prompt) {
|
|
|
13353
14427
|
const estimatedTokens = wordCount * 5;
|
|
13354
14428
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
13355
14429
|
}
|
|
14430
|
+
function hashPrompt(prompt) {
|
|
14431
|
+
let hash = 2166136261;
|
|
14432
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
14433
|
+
hash ^= prompt.charCodeAt(index);
|
|
14434
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
14435
|
+
}
|
|
14436
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
14437
|
+
}
|
|
13356
14438
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
13357
14439
|
var TASK_TYPE_TAGS = {
|
|
13358
14440
|
code: ["code", "instruction"],
|
|
@@ -13396,7 +14478,7 @@ var TaskAnalyzer = class {
|
|
|
13396
14478
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
13397
14479
|
*/
|
|
13398
14480
|
async analyze(prompt) {
|
|
13399
|
-
const cacheKey = prompt
|
|
14481
|
+
const cacheKey = hashPrompt(prompt);
|
|
13400
14482
|
const cached = analysisCache.get(cacheKey);
|
|
13401
14483
|
if (cached) {
|
|
13402
14484
|
this.lastProfile = cached;
|
|
@@ -13495,7 +14577,7 @@ var TaskAnalyzer = class {
|
|
|
13495
14577
|
analysisCache.clear();
|
|
13496
14578
|
}
|
|
13497
14579
|
};
|
|
13498
|
-
var DEFAULT_STATS_FILE =
|
|
14580
|
+
var DEFAULT_STATS_FILE = path26__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
|
|
13499
14581
|
var ModelPerformanceTracker = class {
|
|
13500
14582
|
stats = /* @__PURE__ */ new Map();
|
|
13501
14583
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -13515,7 +14597,7 @@ var ModelPerformanceTracker = class {
|
|
|
13515
14597
|
if (this.loaded) return;
|
|
13516
14598
|
this.loaded = true;
|
|
13517
14599
|
try {
|
|
13518
|
-
const raw = await
|
|
14600
|
+
const raw = await fs5__default.default.readFile(this.statsFile, "utf-8");
|
|
13519
14601
|
const parsed = JSON.parse(raw);
|
|
13520
14602
|
if (parsed.models) {
|
|
13521
14603
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -13535,15 +14617,15 @@ var ModelPerformanceTracker = class {
|
|
|
13535
14617
|
async save() {
|
|
13536
14618
|
if (this.readOnly) return;
|
|
13537
14619
|
try {
|
|
13538
|
-
await
|
|
14620
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(this.statsFile), { recursive: true });
|
|
13539
14621
|
const modelsObj = {};
|
|
13540
14622
|
const featuresObj = {};
|
|
13541
14623
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
13542
14624
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
13543
14625
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
13544
14626
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
13545
|
-
await
|
|
13546
|
-
await
|
|
14627
|
+
await fs5__default.default.writeFile(tmp, json, "utf-8");
|
|
14628
|
+
await fs5__default.default.rename(tmp, this.statsFile);
|
|
13547
14629
|
} catch {
|
|
13548
14630
|
}
|
|
13549
14631
|
}
|
|
@@ -14083,9 +15165,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14083
15165
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
14084
15166
|
async loadPersistedTools() {
|
|
14085
15167
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14086
|
-
const file =
|
|
15168
|
+
const file = path26__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
14087
15169
|
try {
|
|
14088
|
-
const raw = await
|
|
15170
|
+
const raw = await fs5__default.default.readFile(file, "utf-8");
|
|
14089
15171
|
const specs = JSON.parse(raw);
|
|
14090
15172
|
if (!Array.isArray(specs)) return;
|
|
14091
15173
|
let loaded = 0;
|
|
@@ -14107,11 +15189,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14107
15189
|
}
|
|
14108
15190
|
async persist() {
|
|
14109
15191
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14110
|
-
const dir =
|
|
14111
|
-
const file =
|
|
15192
|
+
const dir = path26__default.default.join(this.workspacePath, ".cascade");
|
|
15193
|
+
const file = path26__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14112
15194
|
try {
|
|
14113
|
-
await
|
|
14114
|
-
await
|
|
15195
|
+
await fs5__default.default.mkdir(dir, { recursive: true });
|
|
15196
|
+
await fs5__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
14115
15197
|
} catch (err) {
|
|
14116
15198
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
14117
15199
|
}
|
|
@@ -14128,12 +15210,12 @@ var WorldStateDB = class {
|
|
|
14128
15210
|
constructor(workspacePath, debugMode = false) {
|
|
14129
15211
|
this.workspacePath = workspacePath;
|
|
14130
15212
|
this.debugMode = debugMode;
|
|
14131
|
-
const cascadeDir =
|
|
14132
|
-
if (!
|
|
14133
|
-
|
|
15213
|
+
const cascadeDir = path26__default.default.join(workspacePath, ".cascade");
|
|
15214
|
+
if (!fs25__default.default.existsSync(cascadeDir)) {
|
|
15215
|
+
fs25__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
14134
15216
|
}
|
|
14135
|
-
this.keyPath =
|
|
14136
|
-
this.dbPath =
|
|
15217
|
+
this.keyPath = path26__default.default.join(cascadeDir, "world_state.key");
|
|
15218
|
+
this.dbPath = path26__default.default.join(cascadeDir, "world_state.db");
|
|
14137
15219
|
this.initEncryptionKey();
|
|
14138
15220
|
this.db = new Database__default.default(this.dbPath);
|
|
14139
15221
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14190,11 +15272,11 @@ var WorldStateDB = class {
|
|
|
14190
15272
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14191
15273
|
}
|
|
14192
15274
|
initEncryptionKey() {
|
|
14193
|
-
if (
|
|
14194
|
-
this.encryptionKey =
|
|
15275
|
+
if (fs25__default.default.existsSync(this.keyPath)) {
|
|
15276
|
+
this.encryptionKey = fs25__default.default.readFileSync(this.keyPath);
|
|
14195
15277
|
} else {
|
|
14196
15278
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
14197
|
-
|
|
15279
|
+
fs25__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14198
15280
|
}
|
|
14199
15281
|
}
|
|
14200
15282
|
encrypt(text) {
|
|
@@ -14480,9 +15562,9 @@ var WorldStateDB = class {
|
|
|
14480
15562
|
dumpDebugIfNeeded() {
|
|
14481
15563
|
if (!this.debugMode) return;
|
|
14482
15564
|
try {
|
|
14483
|
-
const dumpPath =
|
|
15565
|
+
const dumpPath = path26__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
14484
15566
|
const entries = this.getAllEntries();
|
|
14485
|
-
|
|
15567
|
+
fs25__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
14486
15568
|
} catch (err) {
|
|
14487
15569
|
console.error("Failed to dump debug world state", err);
|
|
14488
15570
|
}
|
|
@@ -14491,6 +15573,128 @@ var WorldStateDB = class {
|
|
|
14491
15573
|
this.db.close();
|
|
14492
15574
|
}
|
|
14493
15575
|
};
|
|
15576
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
15577
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
15578
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15579
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
15580
|
+
function safeId(taskId) {
|
|
15581
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
15582
|
+
return cleaned.slice(0, 100) || "run";
|
|
15583
|
+
}
|
|
15584
|
+
function isCheckpoint(value) {
|
|
15585
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15586
|
+
const c = value;
|
|
15587
|
+
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);
|
|
15588
|
+
}
|
|
15589
|
+
var ResumeStore = class {
|
|
15590
|
+
dir;
|
|
15591
|
+
maxCheckpoints;
|
|
15592
|
+
maxAgeMs;
|
|
15593
|
+
now;
|
|
15594
|
+
constructor(options) {
|
|
15595
|
+
this.dir = options.dir;
|
|
15596
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
15597
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
15598
|
+
this.now = options.now ?? Date.now;
|
|
15599
|
+
}
|
|
15600
|
+
/**
|
|
15601
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
15602
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
15603
|
+
* worse outcome than the one this store exists to improve.
|
|
15604
|
+
*/
|
|
15605
|
+
async save(checkpoint) {
|
|
15606
|
+
try {
|
|
15607
|
+
const timestamp = this.now();
|
|
15608
|
+
const record = {
|
|
15609
|
+
...checkpoint,
|
|
15610
|
+
version: RESUME_SCHEMA_VERSION,
|
|
15611
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
15612
|
+
};
|
|
15613
|
+
await fs25.promises.mkdir(this.dir, { recursive: true });
|
|
15614
|
+
const finalPath = path26__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
15615
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
15616
|
+
await fs25.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
15617
|
+
await fs25.promises.rename(tempPath, finalPath);
|
|
15618
|
+
await this.prune();
|
|
15619
|
+
} catch {
|
|
15620
|
+
}
|
|
15621
|
+
}
|
|
15622
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
15623
|
+
async list() {
|
|
15624
|
+
let entries;
|
|
15625
|
+
try {
|
|
15626
|
+
entries = await fs25.promises.readdir(this.dir);
|
|
15627
|
+
} catch {
|
|
15628
|
+
return [];
|
|
15629
|
+
}
|
|
15630
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15631
|
+
const candidates = 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);
|
|
15632
|
+
const checkpoints = [];
|
|
15633
|
+
for (const candidate of candidates) {
|
|
15634
|
+
try {
|
|
15635
|
+
const raw = await fs25.promises.readFile(path26__default.default.join(this.dir, candidate.name), "utf-8");
|
|
15636
|
+
const parsed = JSON.parse(raw);
|
|
15637
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
15638
|
+
} catch {
|
|
15639
|
+
continue;
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
return checkpoints;
|
|
15643
|
+
}
|
|
15644
|
+
/** The most recent resumable run, or null. */
|
|
15645
|
+
async latest() {
|
|
15646
|
+
return (await this.list())[0] ?? null;
|
|
15647
|
+
}
|
|
15648
|
+
/**
|
|
15649
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
15650
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
15651
|
+
*/
|
|
15652
|
+
async consume(taskId) {
|
|
15653
|
+
const all = await this.list();
|
|
15654
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
15655
|
+
if (!target) return null;
|
|
15656
|
+
await this.delete(target.taskId);
|
|
15657
|
+
return target;
|
|
15658
|
+
}
|
|
15659
|
+
/** Remove every checkpoint for a task id. */
|
|
15660
|
+
async delete(taskId) {
|
|
15661
|
+
try {
|
|
15662
|
+
const entries = await fs25.promises.readdir(this.dir);
|
|
15663
|
+
const wanted = safeId(taskId);
|
|
15664
|
+
await Promise.all(entries.map(async (name) => {
|
|
15665
|
+
const match = FILE_PATTERN.exec(name);
|
|
15666
|
+
if (match?.[2] === wanted) {
|
|
15667
|
+
await fs25.promises.rm(path26__default.default.join(this.dir, name), { force: true });
|
|
15668
|
+
}
|
|
15669
|
+
}));
|
|
15670
|
+
} catch {
|
|
15671
|
+
}
|
|
15672
|
+
}
|
|
15673
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
15674
|
+
async prune() {
|
|
15675
|
+
try {
|
|
15676
|
+
const entries = await fs25.promises.readdir(this.dir);
|
|
15677
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15678
|
+
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);
|
|
15679
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
15680
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
15681
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
15682
|
+
(name) => fs25.promises.rm(path26__default.default.join(this.dir, name), { force: true })
|
|
15683
|
+
));
|
|
15684
|
+
} catch {
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
};
|
|
15688
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
15689
|
+
if (!completed.length) return "";
|
|
15690
|
+
const lines = completed.map((node) => {
|
|
15691
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
15692
|
+
return `- [${node.status}] ${node.title}
|
|
15693
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
15694
|
+
});
|
|
15695
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
15696
|
+
${lines.join("\n")}`;
|
|
15697
|
+
}
|
|
14494
15698
|
var ignore2 = ignoreFactory__namespace.default ?? ignoreFactory__namespace;
|
|
14495
15699
|
var PrivacyPaths = class {
|
|
14496
15700
|
localOnly;
|
|
@@ -14537,9 +15741,9 @@ var CascadeIgnore = class {
|
|
|
14537
15741
|
]);
|
|
14538
15742
|
}
|
|
14539
15743
|
async load(workspacePath) {
|
|
14540
|
-
const filePath =
|
|
15744
|
+
const filePath = path26__default.default.join(workspacePath, ".cascadeignore");
|
|
14541
15745
|
try {
|
|
14542
|
-
const content = await
|
|
15746
|
+
const content = await fs5__default.default.readFile(filePath, "utf-8");
|
|
14543
15747
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
14544
15748
|
this.ig.add(lines);
|
|
14545
15749
|
this.loaded = true;
|
|
@@ -14548,7 +15752,7 @@ var CascadeIgnore = class {
|
|
|
14548
15752
|
}
|
|
14549
15753
|
isIgnored(filePath, workspacePath) {
|
|
14550
15754
|
try {
|
|
14551
|
-
const relative = workspacePath ?
|
|
15755
|
+
const relative = workspacePath ? path26__default.default.relative(workspacePath, filePath) : filePath;
|
|
14552
15756
|
return this.ig.ignores(relative);
|
|
14553
15757
|
} catch {
|
|
14554
15758
|
return false;
|
|
@@ -14891,7 +16095,7 @@ var WorkspaceIndex = class {
|
|
|
14891
16095
|
isIgnored;
|
|
14892
16096
|
chunker;
|
|
14893
16097
|
constructor(opts) {
|
|
14894
|
-
this.root =
|
|
16098
|
+
this.root = path26__default.default.resolve(opts.root);
|
|
14895
16099
|
this.db = opts.db;
|
|
14896
16100
|
this.namespace = opts.namespace ?? "code";
|
|
14897
16101
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -14918,19 +16122,19 @@ var WorkspaceIndex = class {
|
|
|
14918
16122
|
const walk = async (dir) => {
|
|
14919
16123
|
let entries;
|
|
14920
16124
|
try {
|
|
14921
|
-
entries = await
|
|
16125
|
+
entries = await fs5__default.default.readdir(dir, { withFileTypes: true });
|
|
14922
16126
|
} catch {
|
|
14923
16127
|
return;
|
|
14924
16128
|
}
|
|
14925
16129
|
for (const entry of entries) {
|
|
14926
|
-
const abs =
|
|
16130
|
+
const abs = path26__default.default.join(dir, entry.name);
|
|
14927
16131
|
if (entry.isDirectory()) {
|
|
14928
16132
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
14929
16133
|
await walk(abs);
|
|
14930
16134
|
} else if (entry.isFile()) {
|
|
14931
|
-
const ext =
|
|
16135
|
+
const ext = path26__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
14932
16136
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
14933
|
-
out.push(
|
|
16137
|
+
out.push(path26__default.default.relative(this.root, abs));
|
|
14934
16138
|
}
|
|
14935
16139
|
}
|
|
14936
16140
|
};
|
|
@@ -14960,10 +16164,10 @@ var WorkspaceIndex = class {
|
|
|
14960
16164
|
const entries = [];
|
|
14961
16165
|
for (const rel of rels) {
|
|
14962
16166
|
try {
|
|
14963
|
-
const abs =
|
|
14964
|
-
const stat = await
|
|
16167
|
+
const abs = path26__default.default.join(this.root, rel);
|
|
16168
|
+
const stat = await fs5__default.default.stat(abs);
|
|
14965
16169
|
if (stat.size > this.maxFileBytes) continue;
|
|
14966
|
-
const buf = await
|
|
16170
|
+
const buf = await fs5__default.default.readFile(abs);
|
|
14967
16171
|
if (buf.includes(0)) continue;
|
|
14968
16172
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
14969
16173
|
} catch {
|
|
@@ -15498,8 +16702,16 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15498
16702
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
15499
16703
|
decisionLog = [];
|
|
15500
16704
|
initialized = false;
|
|
15501
|
-
/**
|
|
16705
|
+
/**
|
|
16706
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
16707
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
16708
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
16709
|
+
*/
|
|
15502
16710
|
lastInterruptedRun;
|
|
16711
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
16712
|
+
resumeStore;
|
|
16713
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
16714
|
+
currentRunCompleted = [];
|
|
15503
16715
|
initPromise;
|
|
15504
16716
|
store;
|
|
15505
16717
|
audit;
|
|
@@ -15532,14 +16744,21 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15532
16744
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
15533
16745
|
try {
|
|
15534
16746
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
15535
|
-
fileDeadModelPersistence(
|
|
16747
|
+
fileDeadModelPersistence(path26__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
15536
16748
|
));
|
|
15537
16749
|
} catch {
|
|
15538
16750
|
}
|
|
16751
|
+
try {
|
|
16752
|
+
this.resumeStore = new ResumeStore({
|
|
16753
|
+
dir: path26__default.default.join(workspacePath, ".cascade", "resume")
|
|
16754
|
+
});
|
|
16755
|
+
} catch {
|
|
16756
|
+
}
|
|
15539
16757
|
this.multimodal = new MultimodalRegistry(
|
|
15540
16758
|
(this.config.providers ?? []).map((p) => p.type)
|
|
15541
16759
|
);
|
|
15542
16760
|
this.registerMediaTools(workspacePath);
|
|
16761
|
+
this.registerDocumentTools(workspacePath);
|
|
15543
16762
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15544
16763
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15545
16764
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15619,8 +16838,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15619
16838
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
15620
16839
|
return;
|
|
15621
16840
|
}
|
|
15622
|
-
const dbPath = ci.dbPath ||
|
|
15623
|
-
|
|
16841
|
+
const dbPath = ci.dbPath || path26__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16842
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
15624
16843
|
const db = new Database__default.default(dbPath);
|
|
15625
16844
|
db.pragma("journal_mode = WAL");
|
|
15626
16845
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -15960,6 +17179,35 @@ ${input}` }],
|
|
|
15960
17179
|
this.toolRegistry.register(tool);
|
|
15961
17180
|
}
|
|
15962
17181
|
}
|
|
17182
|
+
/**
|
|
17183
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
17184
|
+
*
|
|
17185
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
17186
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
17187
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
17188
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
17189
|
+
* file_write).
|
|
17190
|
+
*
|
|
17191
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
17192
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
17193
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
17194
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
17195
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
17196
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
17197
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
17198
|
+
*/
|
|
17199
|
+
registerDocumentTools(workspacePath) {
|
|
17200
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
17201
|
+
const tools = buildDocumentTools(async (p) => {
|
|
17202
|
+
const fsp = await import('fs/promises');
|
|
17203
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
17204
|
+
});
|
|
17205
|
+
for (const tool of tools) {
|
|
17206
|
+
if (denied.has(tool.name)) continue;
|
|
17207
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
17208
|
+
this.toolRegistry.register(tool);
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
15963
17211
|
/**
|
|
15964
17212
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
15965
17213
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -15971,10 +17219,43 @@ ${input}` }],
|
|
|
15971
17219
|
setFeedbackSource(source) {
|
|
15972
17220
|
this.router.setFeedbackSource(source);
|
|
15973
17221
|
}
|
|
15974
|
-
/**
|
|
17222
|
+
/**
|
|
17223
|
+
* Write a durable checkpoint for an interrupted run.
|
|
17224
|
+
*
|
|
17225
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
17226
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
17227
|
+
* worse. Nothing here is allowed to throw.
|
|
17228
|
+
*/
|
|
17229
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
17230
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
17231
|
+
try {
|
|
17232
|
+
await this.resumeStore?.save({
|
|
17233
|
+
taskId,
|
|
17234
|
+
prompt,
|
|
17235
|
+
reason,
|
|
17236
|
+
...detail ? { detail } : {},
|
|
17237
|
+
partialOutput,
|
|
17238
|
+
completed: [...this.currentRunCompleted]
|
|
17239
|
+
});
|
|
17240
|
+
} catch {
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17243
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
15975
17244
|
hasResumableRun() {
|
|
15976
17245
|
return this.lastInterruptedRun != null;
|
|
15977
17246
|
}
|
|
17247
|
+
/**
|
|
17248
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
17249
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
17250
|
+
* answer for callers already on a hot path.
|
|
17251
|
+
*/
|
|
17252
|
+
async hasDurableResume() {
|
|
17253
|
+
return await this.resumeStore?.latest() != null;
|
|
17254
|
+
}
|
|
17255
|
+
/** Interrupted runs available to resume, newest first. */
|
|
17256
|
+
async listResumableRuns() {
|
|
17257
|
+
return await this.resumeStore?.list() ?? [];
|
|
17258
|
+
}
|
|
15978
17259
|
/**
|
|
15979
17260
|
* Raise the per-run token budget for a resume and return the continuation
|
|
15980
17261
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -15985,22 +17266,54 @@ ${input}` }],
|
|
|
15985
17266
|
const last = this.lastInterruptedRun;
|
|
15986
17267
|
if (!last) return null;
|
|
15987
17268
|
this.lastInterruptedRun = void 0;
|
|
15988
|
-
|
|
17269
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17270
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
17271
|
+
}
|
|
17272
|
+
/**
|
|
17273
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
17274
|
+
* process, including one that crashed before it could return. Consumes the
|
|
17275
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
17276
|
+
*/
|
|
17277
|
+
async prepareDurableResume(opts = {}) {
|
|
17278
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
17279
|
+
if (!checkpoint) return null;
|
|
17280
|
+
this.lastInterruptedRun = void 0;
|
|
17281
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17282
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
17283
|
+
}
|
|
17284
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
17285
|
+
raiseBudgetForResume(maxTokens) {
|
|
17286
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
15989
17287
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
15990
17288
|
this.router.setMaxTokensPerRun(raised);
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
17289
|
+
}
|
|
17290
|
+
/**
|
|
17291
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
17292
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
17293
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
17294
|
+
* is something it can act on. That difference is the entire reason node
|
|
17295
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
17296
|
+
* finished work again, and the user pays for it a second time.
|
|
17297
|
+
*/
|
|
17298
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
17299
|
+
const done = summarizeCompleted(completed);
|
|
17300
|
+
return [
|
|
17301
|
+
"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.",
|
|
17302
|
+
"",
|
|
17303
|
+
`Original task: ${prompt}`,
|
|
17304
|
+
done ? `
|
|
17305
|
+
${done}` : "",
|
|
17306
|
+
partialOutput ? `
|
|
15995
17307
|
Partial result so far:
|
|
15996
|
-
${
|
|
17308
|
+
${partialOutput}` : ""
|
|
17309
|
+
].filter(Boolean).join("\n");
|
|
15997
17310
|
}
|
|
15998
17311
|
/**
|
|
15999
17312
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
16000
17313
|
* Returns null when there is nothing to resume.
|
|
16001
17314
|
*/
|
|
16002
17315
|
async resumeRun(opts = {}) {
|
|
16003
|
-
const prompt = this.prepareResume(opts);
|
|
17316
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
16004
17317
|
if (!prompt) return null;
|
|
16005
17318
|
return this.run({ prompt });
|
|
16006
17319
|
}
|
|
@@ -16371,6 +17684,7 @@ ${prompt}` : prompt;
|
|
|
16371
17684
|
const startMs = Date.now();
|
|
16372
17685
|
const taskId = crypto4.randomUUID();
|
|
16373
17686
|
this.decisionLog = [];
|
|
17687
|
+
this.currentRunCompleted = [];
|
|
16374
17688
|
if (options.fastAnswer) {
|
|
16375
17689
|
return this.runFastAnswer(options, startMs, taskId);
|
|
16376
17690
|
}
|
|
@@ -16485,6 +17799,11 @@ ${prompt}` : prompt;
|
|
|
16485
17799
|
});
|
|
16486
17800
|
tier.on("log", (e) => this.emit("log", e));
|
|
16487
17801
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
17802
|
+
tier.on("section:complete", (e) => {
|
|
17803
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
17804
|
+
this.currentRunCompleted.push(e);
|
|
17805
|
+
this.emit("section:complete", e);
|
|
17806
|
+
});
|
|
16488
17807
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
16489
17808
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
16490
17809
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -16631,6 +17950,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
16631
17950
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
16632
17951
|
const trip = runBreaker.reason();
|
|
16633
17952
|
if (trip) {
|
|
17953
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
16634
17954
|
finalOutput = [
|
|
16635
17955
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
16636
17956
|
"",
|
|
@@ -16650,6 +17970,13 @@ ${finalOutput}` : ""
|
|
|
16650
17970
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
16651
17971
|
partialOutput: finalOutput || ""
|
|
16652
17972
|
});
|
|
17973
|
+
await this.checkpointRun(
|
|
17974
|
+
taskId,
|
|
17975
|
+
options.prompt,
|
|
17976
|
+
finalOutput || "",
|
|
17977
|
+
"cancelled",
|
|
17978
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
17979
|
+
);
|
|
16653
17980
|
runError = null;
|
|
16654
17981
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
16655
17982
|
this.emit("run:budget-exceeded", {
|
|
@@ -16658,9 +17985,17 @@ ${finalOutput}` : ""
|
|
|
16658
17985
|
partialOutput: finalOutput || ""
|
|
16659
17986
|
});
|
|
16660
17987
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
17988
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
16661
17989
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
16662
17990
|
runError = null;
|
|
16663
17991
|
} else {
|
|
17992
|
+
await this.checkpointRun(
|
|
17993
|
+
taskId,
|
|
17994
|
+
options.prompt,
|
|
17995
|
+
finalOutput || "",
|
|
17996
|
+
"error",
|
|
17997
|
+
err instanceof Error ? err.message : String(err)
|
|
17998
|
+
);
|
|
16664
17999
|
runError = err;
|
|
16665
18000
|
throw err;
|
|
16666
18001
|
}
|
|
@@ -16908,7 +18243,7 @@ var Keystore = class {
|
|
|
16908
18243
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
16909
18244
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
16910
18245
|
this.backend = "keytar";
|
|
16911
|
-
if (password &&
|
|
18246
|
+
if (password && fs25__default.default.existsSync(this.storePath)) {
|
|
16912
18247
|
try {
|
|
16913
18248
|
const fileEntries = this.decryptFile(password);
|
|
16914
18249
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -16927,7 +18262,7 @@ var Keystore = class {
|
|
|
16927
18262
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
16928
18263
|
);
|
|
16929
18264
|
}
|
|
16930
|
-
if (!
|
|
18265
|
+
if (!fs25__default.default.existsSync(this.storePath)) {
|
|
16931
18266
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
16932
18267
|
this.masterKey = this.deriveKey(password, salt);
|
|
16933
18268
|
this.writeWithSalt({}, salt);
|
|
@@ -16941,7 +18276,7 @@ var Keystore = class {
|
|
|
16941
18276
|
}
|
|
16942
18277
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
16943
18278
|
unlockSync(password) {
|
|
16944
|
-
if (!
|
|
18279
|
+
if (!fs25__default.default.existsSync(this.storePath)) {
|
|
16945
18280
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
16946
18281
|
this.masterKey = this.deriveKey(password, salt);
|
|
16947
18282
|
this.writeWithSalt({}, salt);
|
|
@@ -16999,7 +18334,7 @@ var Keystore = class {
|
|
|
16999
18334
|
}
|
|
17000
18335
|
}
|
|
17001
18336
|
decryptFile(password, knownSalt) {
|
|
17002
|
-
if (!
|
|
18337
|
+
if (!fs25__default.default.existsSync(this.storePath)) return {};
|
|
17003
18338
|
try {
|
|
17004
18339
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
17005
18340
|
const useSalt = knownSalt ?? salt;
|
|
@@ -17021,8 +18356,8 @@ var Keystore = class {
|
|
|
17021
18356
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17022
18357
|
const tag = cipher.getAuthTag();
|
|
17023
18358
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
17024
|
-
|
|
17025
|
-
|
|
18359
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
18360
|
+
fs25__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17026
18361
|
}
|
|
17027
18362
|
writeWithSalt(data, salt) {
|
|
17028
18363
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -17032,11 +18367,11 @@ var Keystore = class {
|
|
|
17032
18367
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17033
18368
|
const tag = cipher.getAuthTag();
|
|
17034
18369
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
17035
|
-
|
|
17036
|
-
|
|
18370
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(this.storePath), { recursive: true });
|
|
18371
|
+
fs25__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
17037
18372
|
}
|
|
17038
18373
|
readRaw() {
|
|
17039
|
-
const buf =
|
|
18374
|
+
const buf = fs25__default.default.readFileSync(this.storePath);
|
|
17040
18375
|
let offset = 0;
|
|
17041
18376
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
17042
18377
|
offset += SALT_LEN;
|
|
@@ -17052,9 +18387,9 @@ var Keystore = class {
|
|
|
17052
18387
|
}
|
|
17053
18388
|
};
|
|
17054
18389
|
async function loadCascadeMd(workspacePath) {
|
|
17055
|
-
const filePath =
|
|
18390
|
+
const filePath = path26__default.default.join(workspacePath, "CASCADE.md");
|
|
17056
18391
|
try {
|
|
17057
|
-
const raw = await
|
|
18392
|
+
const raw = await fs5__default.default.readFile(filePath, "utf-8");
|
|
17058
18393
|
return parseCascadeMd(raw);
|
|
17059
18394
|
} catch {
|
|
17060
18395
|
return null;
|
|
@@ -17083,7 +18418,7 @@ ${raw.trim()}`;
|
|
|
17083
18418
|
var MemoryStore = class _MemoryStore {
|
|
17084
18419
|
db;
|
|
17085
18420
|
constructor(dbPath) {
|
|
17086
|
-
|
|
18421
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(dbPath), { recursive: true });
|
|
17087
18422
|
try {
|
|
17088
18423
|
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
17089
18424
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17919,7 +19254,7 @@ Original error: ${err.message}`
|
|
|
17919
19254
|
}
|
|
17920
19255
|
};
|
|
17921
19256
|
function credentialsPath(globalDir2) {
|
|
17922
|
-
return
|
|
19257
|
+
return path26__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
17923
19258
|
}
|
|
17924
19259
|
function providerKey(p) {
|
|
17925
19260
|
if (p.type === "azure") {
|
|
@@ -17932,7 +19267,7 @@ function isPersistable(p) {
|
|
|
17932
19267
|
}
|
|
17933
19268
|
function loadGlobalCredentials(globalDir2) {
|
|
17934
19269
|
try {
|
|
17935
|
-
const raw =
|
|
19270
|
+
const raw = fs25__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
17936
19271
|
const parsed = JSON.parse(raw);
|
|
17937
19272
|
if (!Array.isArray(parsed.providers)) return [];
|
|
17938
19273
|
return parsed.providers.filter(
|
|
@@ -17944,13 +19279,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
17944
19279
|
}
|
|
17945
19280
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
17946
19281
|
const filePath = credentialsPath(globalDir2);
|
|
17947
|
-
|
|
19282
|
+
fs25__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
17948
19283
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
17949
19284
|
const tmp = `${filePath}.tmp`;
|
|
17950
|
-
|
|
17951
|
-
|
|
19285
|
+
fs25__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
19286
|
+
fs25__default.default.renameSync(tmp, filePath);
|
|
17952
19287
|
try {
|
|
17953
|
-
|
|
19288
|
+
fs25__default.default.chmodSync(filePath, 384);
|
|
17954
19289
|
} catch {
|
|
17955
19290
|
}
|
|
17956
19291
|
}
|
|
@@ -17985,7 +19320,7 @@ var ConfigManager = class {
|
|
|
17985
19320
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
17986
19321
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
17987
19322
|
this.workspacePath = workspacePath;
|
|
17988
|
-
this.globalDir = globalDirOverride ??
|
|
19323
|
+
this.globalDir = globalDirOverride ?? path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
17989
19324
|
}
|
|
17990
19325
|
async load() {
|
|
17991
19326
|
this.config = await this.loadConfig();
|
|
@@ -17999,8 +19334,8 @@ var ConfigManager = class {
|
|
|
17999
19334
|
this.ignore = new CascadeIgnore();
|
|
18000
19335
|
await this.ignore.load(this.workspacePath);
|
|
18001
19336
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
18002
|
-
this.keystore = new Keystore(
|
|
18003
|
-
this.store = new MemoryStore(
|
|
19337
|
+
this.keystore = new Keystore(path26__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
19338
|
+
this.store = new MemoryStore(path26__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
18004
19339
|
await this.injectEnvKeys();
|
|
18005
19340
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
18006
19341
|
await this.ensureDefaultIdentity();
|
|
@@ -18025,9 +19360,9 @@ var ConfigManager = class {
|
|
|
18025
19360
|
return this.workspacePath;
|
|
18026
19361
|
}
|
|
18027
19362
|
async save() {
|
|
18028
|
-
const configPath =
|
|
18029
|
-
await
|
|
18030
|
-
await
|
|
19363
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19364
|
+
await fs5__default.default.mkdir(path26__default.default.dirname(configPath), { recursive: true });
|
|
19365
|
+
await fs5__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18031
19366
|
try {
|
|
18032
19367
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
18033
19368
|
} catch (err) {
|
|
@@ -18062,9 +19397,9 @@ var ConfigManager = class {
|
|
|
18062
19397
|
return configProvider?.apiKey;
|
|
18063
19398
|
}
|
|
18064
19399
|
async loadConfig() {
|
|
18065
|
-
const configPath =
|
|
19400
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18066
19401
|
try {
|
|
18067
|
-
const raw = await
|
|
19402
|
+
const raw = await fs5__default.default.readFile(configPath, "utf-8");
|
|
18068
19403
|
return validateConfig(JSON.parse(raw));
|
|
18069
19404
|
} catch (err) {
|
|
18070
19405
|
if (err.code === "ENOENT") {
|
|
@@ -18594,7 +19929,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
18594
19929
|
}
|
|
18595
19930
|
|
|
18596
19931
|
// src/dashboard/server.ts
|
|
18597
|
-
var __dirname$1 =
|
|
19932
|
+
var __dirname$1 = path26__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
18598
19933
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
18599
19934
|
var DashboardServer = class {
|
|
18600
19935
|
app;
|
|
@@ -18874,14 +20209,14 @@ var DashboardServer = class {
|
|
|
18874
20209
|
*/
|
|
18875
20210
|
persistConfig() {
|
|
18876
20211
|
try {
|
|
18877
|
-
const configPath =
|
|
18878
|
-
|
|
18879
|
-
|
|
20212
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20213
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(configPath), { recursive: true });
|
|
20214
|
+
fs25__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18880
20215
|
} catch (err) {
|
|
18881
20216
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18882
20217
|
}
|
|
18883
20218
|
try {
|
|
18884
|
-
saveGlobalCredentials(
|
|
20219
|
+
saveGlobalCredentials(path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
18885
20220
|
} catch (err) {
|
|
18886
20221
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
18887
20222
|
}
|
|
@@ -18896,15 +20231,15 @@ var DashboardServer = class {
|
|
|
18896
20231
|
resolveDashboardSecret() {
|
|
18897
20232
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
18898
20233
|
if (fromConfig) return fromConfig;
|
|
18899
|
-
const secretPath =
|
|
20234
|
+
const secretPath = path26__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
18900
20235
|
try {
|
|
18901
|
-
if (
|
|
18902
|
-
const existing =
|
|
20236
|
+
if (fs25__default.default.existsSync(secretPath)) {
|
|
20237
|
+
const existing = fs25__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
18903
20238
|
if (existing.length >= 16) return existing;
|
|
18904
20239
|
}
|
|
18905
20240
|
const generated = crypto4.randomUUID();
|
|
18906
|
-
|
|
18907
|
-
|
|
20241
|
+
fs25__default.default.mkdirSync(path26__default.default.dirname(secretPath), { recursive: true });
|
|
20242
|
+
fs25__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
18908
20243
|
if (this.config.dashboard.auth) {
|
|
18909
20244
|
console.warn(
|
|
18910
20245
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -18931,7 +20266,7 @@ var DashboardServer = class {
|
|
|
18931
20266
|
// ── Setup ─────────────────────────────────────
|
|
18932
20267
|
getGlobalStore() {
|
|
18933
20268
|
if (!this.globalStore) {
|
|
18934
|
-
const globalDbPath =
|
|
20269
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
18935
20270
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
18936
20271
|
}
|
|
18937
20272
|
return this.globalStore;
|
|
@@ -19311,12 +20646,12 @@ ${prompt}`;
|
|
|
19311
20646
|
}
|
|
19312
20647
|
}
|
|
19313
20648
|
watchRuntimeChanges() {
|
|
19314
|
-
const workspaceDbPath =
|
|
19315
|
-
const globalDbPath =
|
|
20649
|
+
const workspaceDbPath = path26__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20650
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19316
20651
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
19317
20652
|
for (const watchPath of watchPaths) {
|
|
19318
|
-
if (!
|
|
19319
|
-
|
|
20653
|
+
if (!fs25__default.default.existsSync(watchPath)) continue;
|
|
20654
|
+
fs25__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
19320
20655
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
19321
20656
|
});
|
|
19322
20657
|
}
|
|
@@ -19438,7 +20773,7 @@ ${prompt}`;
|
|
|
19438
20773
|
const sessionId = req.params.id;
|
|
19439
20774
|
this.store.deleteSession(sessionId);
|
|
19440
20775
|
this.store.deleteRuntimeSession(sessionId);
|
|
19441
|
-
const globalDbPath =
|
|
20776
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19442
20777
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19443
20778
|
try {
|
|
19444
20779
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -19531,7 +20866,7 @@ ${prompt}`;
|
|
|
19531
20866
|
});
|
|
19532
20867
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
19533
20868
|
const body = req.body;
|
|
19534
|
-
const globalDbPath =
|
|
20869
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19535
20870
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
19536
20871
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19537
20872
|
try {
|
|
@@ -19554,7 +20889,7 @@ ${prompt}`;
|
|
|
19554
20889
|
});
|
|
19555
20890
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
19556
20891
|
this.store.deleteAllRuntimeNodes();
|
|
19557
|
-
const globalDbPath =
|
|
20892
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19558
20893
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19559
20894
|
try {
|
|
19560
20895
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -19640,12 +20975,12 @@ ${prompt}`;
|
|
|
19640
20975
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
19641
20976
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
19642
20977
|
try {
|
|
19643
|
-
const configPath =
|
|
19644
|
-
const existing =
|
|
20978
|
+
const configPath = path26__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20979
|
+
const existing = fs25__default.default.existsSync(configPath) ? JSON.parse(fs25__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
19645
20980
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
19646
20981
|
const tmp = configPath + ".tmp";
|
|
19647
|
-
|
|
19648
|
-
|
|
20982
|
+
fs25__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20983
|
+
fs25__default.default.renameSync(tmp, configPath);
|
|
19649
20984
|
res.json({ ok: true });
|
|
19650
20985
|
} catch (err) {
|
|
19651
20986
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -19673,7 +21008,7 @@ ${prompt}`;
|
|
|
19673
21008
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
19674
21009
|
const scope = req.query["scope"] ?? "workspace";
|
|
19675
21010
|
if (scope === "global") {
|
|
19676
|
-
const globalDbPath =
|
|
21011
|
+
const globalDbPath = path26__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19677
21012
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19678
21013
|
try {
|
|
19679
21014
|
res.json({
|
|
@@ -20014,13 +21349,13 @@ ${prompt}`;
|
|
|
20014
21349
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
20015
21350
|
}
|
|
20016
21351
|
});
|
|
20017
|
-
const prodPath =
|
|
20018
|
-
const devPath =
|
|
20019
|
-
const webDistPath =
|
|
20020
|
-
if (
|
|
21352
|
+
const prodPath = path26__default.default.resolve(__dirname$1, "../web/dist");
|
|
21353
|
+
const devPath = path26__default.default.resolve(__dirname$1, "../../web/dist");
|
|
21354
|
+
const webDistPath = fs25__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
21355
|
+
if (fs25__default.default.existsSync(webDistPath)) {
|
|
20021
21356
|
this.app.use(express__default.default.static(webDistPath));
|
|
20022
21357
|
this.app.get("*", (_req, res) => {
|
|
20023
|
-
res.sendFile(
|
|
21358
|
+
res.sendFile(path26__default.default.join(webDistPath, "index.html"));
|
|
20024
21359
|
});
|
|
20025
21360
|
} else {
|
|
20026
21361
|
this.app.get("/", (_req, res) => {
|
|
@@ -20207,14 +21542,14 @@ function planRetrieval(ctx) {
|
|
|
20207
21542
|
}
|
|
20208
21543
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20209
21544
|
function globalDir(homeDir = os6__default.default.homedir()) {
|
|
20210
|
-
return
|
|
21545
|
+
return path26__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20211
21546
|
}
|
|
20212
21547
|
function cloudSessionPath(dir = globalDir()) {
|
|
20213
|
-
return
|
|
21548
|
+
return path26__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
20214
21549
|
}
|
|
20215
21550
|
function loadCloudSession(dir = globalDir()) {
|
|
20216
21551
|
try {
|
|
20217
|
-
const raw =
|
|
21552
|
+
const raw = fs25__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20218
21553
|
const parsed = JSON.parse(raw);
|
|
20219
21554
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20220
21555
|
return parsed;
|
|
@@ -20223,17 +21558,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20223
21558
|
}
|
|
20224
21559
|
}
|
|
20225
21560
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20226
|
-
|
|
21561
|
+
fs25__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20227
21562
|
const file = cloudSessionPath(dir);
|
|
20228
|
-
|
|
21563
|
+
fs25__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20229
21564
|
try {
|
|
20230
|
-
|
|
21565
|
+
fs25__default.default.chmodSync(file, 384);
|
|
20231
21566
|
} catch {
|
|
20232
21567
|
}
|
|
20233
21568
|
}
|
|
20234
21569
|
function clearCloudSession(dir = globalDir()) {
|
|
20235
21570
|
try {
|
|
20236
|
-
|
|
21571
|
+
fs25__default.default.rmSync(cloudSessionPath(dir));
|
|
20237
21572
|
} catch {
|
|
20238
21573
|
}
|
|
20239
21574
|
}
|
|
@@ -20405,16 +21740,16 @@ var CloudClient = class _CloudClient {
|
|
|
20405
21740
|
return (await this.refresh()).accessToken;
|
|
20406
21741
|
}
|
|
20407
21742
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20408
|
-
async authed(
|
|
20409
|
-
const call = (token) => fetch(this.url(
|
|
21743
|
+
async authed(path28, init = {}) {
|
|
21744
|
+
const call = (token) => fetch(this.url(path28), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20410
21745
|
let res = await call(await this.accessToken());
|
|
20411
21746
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20412
21747
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20413
21748
|
const text = await res.text();
|
|
20414
21749
|
return text ? JSON.parse(text) : {};
|
|
20415
21750
|
}
|
|
20416
|
-
authedGet(
|
|
20417
|
-
return this.authed(
|
|
21751
|
+
authedGet(path28) {
|
|
21752
|
+
return this.authed(path28);
|
|
20418
21753
|
}
|
|
20419
21754
|
async fetchMe(token) {
|
|
20420
21755
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20783,7 +22118,9 @@ exports.DEFAULT_DASHBOARD_PORT = DEFAULT_DASHBOARD_PORT;
|
|
|
20783
22118
|
exports.DEFAULT_MAX_SESSION_MESSAGES = DEFAULT_MAX_SESSION_MESSAGES;
|
|
20784
22119
|
exports.DEFAULT_RETENTION_DAYS = DEFAULT_RETENTION_DAYS;
|
|
20785
22120
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
22121
|
+
exports.DOCUMENT_MIME = DOCUMENT_MIME;
|
|
20786
22122
|
exports.DashboardServer = DashboardServer;
|
|
22123
|
+
exports.DependencyScheduler = DependencyScheduler;
|
|
20787
22124
|
exports.FileMcpOAuthStore = FileMcpOAuthStore;
|
|
20788
22125
|
exports.GITHUB_MODELS_API_VERSION = GITHUB_MODELS_API_VERSION;
|
|
20789
22126
|
exports.GITHUB_MODELS_CATALOG_URL = GITHUB_MODELS_CATALOG_URL;
|
|
@@ -20793,6 +22130,7 @@ exports.GLOBAL_CREDENTIALS_FILE = GLOBAL_CREDENTIALS_FILE;
|
|
|
20793
22130
|
exports.GLOBAL_DB_FILE = GLOBAL_DB_FILE;
|
|
20794
22131
|
exports.GLOBAL_KEYSTORE_FILE = GLOBAL_KEYSTORE_FILE;
|
|
20795
22132
|
exports.GLOBAL_RUNTIME_DB_FILE = GLOBAL_RUNTIME_DB_FILE;
|
|
22133
|
+
exports.GenerateDocumentTool = GenerateDocumentTool;
|
|
20796
22134
|
exports.GraphRetriever = GraphRetriever;
|
|
20797
22135
|
exports.GraphSearchTool = GraphSearchTool;
|
|
20798
22136
|
exports.HooksRunner = HooksRunner;
|
|
@@ -20808,6 +22146,8 @@ exports.MemoryStore = MemoryStore;
|
|
|
20808
22146
|
exports.OLLAMA_BASE_URL = OLLAMA_BASE_URL;
|
|
20809
22147
|
exports.OpenAICompatibleEmbedder = OpenAICompatibleEmbedder;
|
|
20810
22148
|
exports.PROVIDER_DISPLAY_NAMES = PROVIDER_DISPLAY_NAMES;
|
|
22149
|
+
exports.RESUME_SCHEMA_VERSION = RESUME_SCHEMA_VERSION;
|
|
22150
|
+
exports.ResumeStore = ResumeStore;
|
|
20811
22151
|
exports.Retriever = Retriever;
|
|
20812
22152
|
exports.SqliteVectorStore = SqliteVectorStore;
|
|
20813
22153
|
exports.T1Administrator = T1Administrator;
|
|
@@ -20818,6 +22158,7 @@ exports.T3Worker = T3Worker;
|
|
|
20818
22158
|
exports.T3_MODEL_PRIORITY = T3_MODEL_PRIORITY;
|
|
20819
22159
|
exports.THEME_NAMES = THEME_NAMES;
|
|
20820
22160
|
exports.TOOL_NAMES = TOOL_NAMES;
|
|
22161
|
+
exports.TaskGraphValidationError = TaskGraphValidationError;
|
|
20821
22162
|
exports.TaskScheduler = TaskScheduler;
|
|
20822
22163
|
exports.Telemetry = Telemetry;
|
|
20823
22164
|
exports.ToolRegistry = ToolRegistry;
|
|
@@ -20826,12 +22167,19 @@ exports.WorkspaceIndex = WorkspaceIndex;
|
|
|
20826
22167
|
exports.applySyncBundle = applySyncBundle;
|
|
20827
22168
|
exports.azureModelForDeployment = azureModelForDeployment;
|
|
20828
22169
|
exports.beginMcpOAuth = beginMcpOAuth;
|
|
22170
|
+
exports.buildDocumentTools = buildDocumentTools;
|
|
20829
22171
|
exports.buildManifest = buildManifest;
|
|
20830
22172
|
exports.buildSessionTranscript = buildSessionTranscript;
|
|
22173
|
+
exports.bytesToBase64 = bytesToBase64;
|
|
20831
22174
|
exports.cagCharBudget = cagCharBudget;
|
|
22175
|
+
exports.chartKind = chartKind;
|
|
22176
|
+
exports.chartToTableRows = chartToTableRows;
|
|
20832
22177
|
exports.chatCompleterFromProviders = chatCompleterFromProviders;
|
|
20833
22178
|
exports.chunkCode = chunkCode;
|
|
20834
22179
|
exports.chunkText = chunkText2;
|
|
22180
|
+
exports.compileSectionGraph = compileSectionGraph;
|
|
22181
|
+
exports.compileSubtaskGraph = compileSubtaskGraph;
|
|
22182
|
+
exports.compileTaskGraph = compileTaskGraph;
|
|
20835
22183
|
exports.completeMcpOAuth = completeMcpOAuth;
|
|
20836
22184
|
exports.connectMcpWithLoopbackOAuth = connectMcpWithLoopbackOAuth;
|
|
20837
22185
|
exports.createCascade = createCascade;
|
|
@@ -20843,24 +22191,44 @@ exports.discoverMcpTools = discoverMcpTools;
|
|
|
20843
22191
|
exports.distillSessionFacts = distillSessionFacts;
|
|
20844
22192
|
exports.embedderFromProviders = embedderFromProviders;
|
|
20845
22193
|
exports.encryptSyncBlob = encryptJSON;
|
|
22194
|
+
exports.evaluateAcceptance = evaluateAcceptance;
|
|
22195
|
+
exports.extractCharts = extractCharts;
|
|
22196
|
+
exports.failures = failures;
|
|
22197
|
+
exports.fileExt = fileExt;
|
|
20846
22198
|
exports.fileOAuthProvider = fileOAuthProvider;
|
|
20847
22199
|
exports.gatherSyncBundle = gatherSyncBundle;
|
|
20848
22200
|
exports.hashContent = hashContent;
|
|
20849
22201
|
exports.heuristicCodeChunker = heuristicCodeChunker;
|
|
20850
22202
|
exports.inferAzureBaseModel = inferAzureBaseModel;
|
|
22203
|
+
exports.inlineRuns = inlineRuns;
|
|
22204
|
+
exports.isDocumentFormat = isDocumentFormat;
|
|
20851
22205
|
exports.isMcpToolName = isMcpToolName;
|
|
22206
|
+
exports.matchImageLine = matchImageLine;
|
|
20852
22207
|
exports.mcpServerPrefix = mcpServerPrefix;
|
|
20853
22208
|
exports.mcpToolName = mcpToolName;
|
|
20854
22209
|
exports.nodeHttpFetch = nodeHttpFetch;
|
|
22210
|
+
exports.parseBlocks = parseBlocks;
|
|
22211
|
+
exports.parseChartSpec = parseChartSpec;
|
|
22212
|
+
exports.parseDelimited = parseDelimited;
|
|
20855
22213
|
exports.parseRankOrder = parseRankOrder;
|
|
22214
|
+
exports.parseSlide = parseSlide;
|
|
20856
22215
|
exports.planRetrieval = planRetrieval;
|
|
20857
22216
|
exports.preferIpv4Host = preferIpv4Host;
|
|
20858
22217
|
exports.reciprocalRankFusion = reciprocalRankFusion;
|
|
20859
22218
|
exports.refreshMcpToken = refreshMcpToken;
|
|
20860
22219
|
exports.removeMcpServerDenials = removeMcpServerDenials;
|
|
22220
|
+
exports.renderDocument = renderDocument;
|
|
22221
|
+
exports.renderDocx = renderDocx;
|
|
22222
|
+
exports.renderPptx = renderPptx;
|
|
22223
|
+
exports.renderXlsx = renderXlsx;
|
|
20861
22224
|
exports.runCascade = runCascade;
|
|
20862
22225
|
exports.sessionWorthRemembering = sessionWorthRemembering;
|
|
22226
|
+
exports.sniffImage = sniffImage;
|
|
22227
|
+
exports.splitSlides = splitSlides;
|
|
20863
22228
|
exports.streamCascade = streamCascade;
|
|
22229
|
+
exports.stripInline = stripInline;
|
|
22230
|
+
exports.summarizeCompleted = summarizeCompleted;
|
|
22231
|
+
exports.undecided = undecided;
|
|
20864
22232
|
exports.uniqueMcpServerName = uniqueMcpServerName;
|
|
20865
22233
|
//# sourceMappingURL=index.cjs.map
|
|
20866
22234
|
//# sourceMappingURL=index.cjs.map
|