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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path26, { dirname } from 'path';
|
|
3
|
+
import fs25, { promises, mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import os6 from 'os';
|
|
5
5
|
import crypto4, { randomUUID, randomBytes, createHash, timingSafeEqual, webcrypto } from 'crypto';
|
|
6
6
|
import EventEmitter, { setMaxListeners } from 'events';
|
|
@@ -13,7 +13,7 @@ import http, { createServer } from 'http';
|
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import zlib from 'zlib';
|
|
15
15
|
import { Readable } from 'stream';
|
|
16
|
-
import
|
|
16
|
+
import fs5 from 'fs/promises';
|
|
17
17
|
import * as ignoreFactory from 'ignore';
|
|
18
18
|
import ignoreFactory__default from 'ignore';
|
|
19
19
|
import { exec, execFile, execSync } from 'child_process';
|
|
@@ -61,12 +61,12 @@ var init_audit_logger = __esm({
|
|
|
61
61
|
constructor(workspacePath, debugMode = false) {
|
|
62
62
|
this.workspacePath = workspacePath;
|
|
63
63
|
this.debugMode = debugMode;
|
|
64
|
-
const cascadeDir =
|
|
65
|
-
if (!
|
|
66
|
-
|
|
64
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
65
|
+
if (!fs25.existsSync(cascadeDir)) {
|
|
66
|
+
fs25.mkdirSync(cascadeDir, { recursive: true });
|
|
67
67
|
}
|
|
68
|
-
this.keyPath =
|
|
69
|
-
this.dbPath =
|
|
68
|
+
this.keyPath = path26.join(cascadeDir, "audit_log.key");
|
|
69
|
+
this.dbPath = path26.join(cascadeDir, "audit_log.db");
|
|
70
70
|
this.initEncryptionKey();
|
|
71
71
|
this.db = new Database(this.dbPath);
|
|
72
72
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -96,11 +96,11 @@ var init_audit_logger = __esm({
|
|
|
96
96
|
return crypto4.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
97
97
|
}
|
|
98
98
|
initEncryptionKey() {
|
|
99
|
-
if (
|
|
100
|
-
this.encryptionKey =
|
|
99
|
+
if (fs25.existsSync(this.keyPath)) {
|
|
100
|
+
this.encryptionKey = fs25.readFileSync(this.keyPath);
|
|
101
101
|
} else {
|
|
102
102
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
103
|
-
|
|
103
|
+
fs25.writeFileSync(this.keyPath, this.encryptionKey);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
encrypt(text) {
|
|
@@ -178,9 +178,9 @@ var init_audit_logger = __esm({
|
|
|
178
178
|
dumpDebugIfNeeded() {
|
|
179
179
|
if (!this.debugMode) return;
|
|
180
180
|
try {
|
|
181
|
-
const dumpPath =
|
|
181
|
+
const dumpPath = path26.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
|
|
182
182
|
const entries = this.getAllLogs();
|
|
183
|
-
|
|
183
|
+
fs25.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
184
184
|
} catch (err) {
|
|
185
185
|
console.error("Failed to dump debug audit logs", err);
|
|
186
186
|
}
|
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.68.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4617,7 +4617,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4617
4617
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4618
4618
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4619
4619
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4620
|
-
var DEFAULT_CACHE_FILE =
|
|
4620
|
+
var DEFAULT_CACHE_FILE = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4621
4621
|
function normalizeModelId2(id) {
|
|
4622
4622
|
let s = id.toLowerCase();
|
|
4623
4623
|
const slash = s.lastIndexOf("/");
|
|
@@ -4652,7 +4652,7 @@ var LiveDataProvider = class {
|
|
|
4652
4652
|
if (this.loaded) return;
|
|
4653
4653
|
this.loaded = true;
|
|
4654
4654
|
try {
|
|
4655
|
-
const raw = await
|
|
4655
|
+
const raw = await fs5.readFile(this.opts.cacheFile, "utf-8");
|
|
4656
4656
|
const cache = JSON.parse(raw);
|
|
4657
4657
|
if (cache.snapshot?.families) {
|
|
4658
4658
|
this.snapshot = cache.snapshot;
|
|
@@ -4764,14 +4764,14 @@ var LiveDataProvider = class {
|
|
|
4764
4764
|
}
|
|
4765
4765
|
async saveCache() {
|
|
4766
4766
|
try {
|
|
4767
|
-
await
|
|
4767
|
+
await fs5.mkdir(path26.dirname(this.opts.cacheFile), { recursive: true });
|
|
4768
4768
|
const cache = {
|
|
4769
4769
|
fetchedAt: this.fetchedAt,
|
|
4770
4770
|
snapshot: this.snapshot ?? void 0,
|
|
4771
4771
|
prices: Object.fromEntries(this.prices),
|
|
4772
4772
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4773
4773
|
};
|
|
4774
|
-
await
|
|
4774
|
+
await fs5.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4775
4775
|
} catch {
|
|
4776
4776
|
}
|
|
4777
4777
|
}
|
|
@@ -6556,6 +6556,79 @@ function describeProviderError(c, modelId) {
|
|
|
6556
6556
|
}
|
|
6557
6557
|
}
|
|
6558
6558
|
|
|
6559
|
+
// src/core/verification/acceptance.ts
|
|
6560
|
+
var PATH_TOKEN = /[\w./\\-]{0,200}\w\.[A-Za-z0-9]{1,8}\b/g;
|
|
6561
|
+
var QUOTED_NEEDLE = /(?:contains?|includes?|mentions?)\s+["'`]([^"'`]+)["'`]/i;
|
|
6562
|
+
var EXISTS = /\b(?:exists?|is\s+created|is\s+present|was\s+created|be\s+created)\b/i;
|
|
6563
|
+
var NON_EMPTY = /\b(?:non-?empty|not\s+empty)\b/i;
|
|
6564
|
+
var NEGATION = /\b(?:not|no|never|without|absent|missing|does\s*n[o']t)\b/i;
|
|
6565
|
+
function targetFile(criterion, ownedFiles) {
|
|
6566
|
+
const mentioned = [...new Set(criterion.match(PATH_TOKEN) ?? [])];
|
|
6567
|
+
if (mentioned.length > 1) return null;
|
|
6568
|
+
if (mentioned.length === 1) {
|
|
6569
|
+
const candidate = mentioned[0];
|
|
6570
|
+
return ownedFiles.find((file) => file === candidate || file.endsWith(`/${candidate}`)) ?? candidate;
|
|
6571
|
+
}
|
|
6572
|
+
return ownedFiles.length === 1 ? ownedFiles[0] : null;
|
|
6573
|
+
}
|
|
6574
|
+
async function evaluateAcceptance(criteria, ownedFiles, probe) {
|
|
6575
|
+
const results = [];
|
|
6576
|
+
for (const criterion of criteria) {
|
|
6577
|
+
const text = criterion.trim();
|
|
6578
|
+
if (!text) {
|
|
6579
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6580
|
+
continue;
|
|
6581
|
+
}
|
|
6582
|
+
if (NEGATION.test(text)) {
|
|
6583
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6584
|
+
continue;
|
|
6585
|
+
}
|
|
6586
|
+
const needle = text.match(QUOTED_NEEDLE)?.[1];
|
|
6587
|
+
const wantsExists = EXISTS.test(text);
|
|
6588
|
+
const wantsNonEmpty = NON_EMPTY.test(text);
|
|
6589
|
+
if (!needle && !wantsExists && !wantsNonEmpty) {
|
|
6590
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6591
|
+
continue;
|
|
6592
|
+
}
|
|
6593
|
+
const file = targetFile(text, ownedFiles);
|
|
6594
|
+
if (!file) {
|
|
6595
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6596
|
+
continue;
|
|
6597
|
+
}
|
|
6598
|
+
const stat = await probe.stat(file);
|
|
6599
|
+
if (!stat) {
|
|
6600
|
+
results.push({ criterion, verdict: "failed", detail: `${file} does not exist` });
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
if (wantsNonEmpty && stat.size === 0) {
|
|
6604
|
+
results.push({ criterion, verdict: "failed", detail: `${file} is empty` });
|
|
6605
|
+
continue;
|
|
6606
|
+
}
|
|
6607
|
+
if (needle) {
|
|
6608
|
+
const content = await probe.read(file);
|
|
6609
|
+
if (content === null) {
|
|
6610
|
+
results.push({ criterion, verdict: "undecidable" });
|
|
6611
|
+
continue;
|
|
6612
|
+
}
|
|
6613
|
+
const found = content.includes(needle);
|
|
6614
|
+
results.push({
|
|
6615
|
+
criterion,
|
|
6616
|
+
verdict: found ? "passed" : "failed",
|
|
6617
|
+
detail: found ? `${file} contains ${JSON.stringify(needle)}` : `${file} does not contain ${JSON.stringify(needle)}`
|
|
6618
|
+
});
|
|
6619
|
+
continue;
|
|
6620
|
+
}
|
|
6621
|
+
results.push({ criterion, verdict: "passed", detail: `${file} exists` });
|
|
6622
|
+
}
|
|
6623
|
+
return results;
|
|
6624
|
+
}
|
|
6625
|
+
function undecided(results) {
|
|
6626
|
+
return results.filter((r) => r.verdict === "undecidable").map((r) => r.criterion);
|
|
6627
|
+
}
|
|
6628
|
+
function failures(results) {
|
|
6629
|
+
return results.filter((r) => r.verdict === "failed").map((r) => `Acceptance not met \u2014 ${r.criterion}${r.detail ? ` (${r.detail})` : ""}`);
|
|
6630
|
+
}
|
|
6631
|
+
|
|
6559
6632
|
// src/core/tiers/t3-worker.ts
|
|
6560
6633
|
var CriticalToolError = class extends Error {
|
|
6561
6634
|
constructor(message, toolName) {
|
|
@@ -6598,7 +6671,8 @@ var KNOWN_TOOLS = [
|
|
|
6598
6671
|
"generate_image",
|
|
6599
6672
|
"generate_video",
|
|
6600
6673
|
"generate_speech",
|
|
6601
|
-
"transcribe_audio"
|
|
6674
|
+
"transcribe_audio",
|
|
6675
|
+
"generate_document"
|
|
6602
6676
|
];
|
|
6603
6677
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
6604
6678
|
"generate_image",
|
|
@@ -6615,18 +6689,45 @@ function buildWorkerRules(has) {
|
|
|
6615
6689
|
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.",
|
|
6616
6690
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
6617
6691
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
6692
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
6693
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
6694
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
6695
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
6696
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
6697
|
+
// alone competes with a file_write habit the model already has.
|
|
6698
|
+
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.',
|
|
6699
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
6700
|
+
// describing a chart that does not exist, which is what real decks came
|
|
6701
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
6702
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
6703
|
+
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.',
|
|
6618
6704
|
// Without this the model "writes the image" as prose — a bracketed
|
|
6619
6705
|
// description sitting in the deck where the picture should be — even with a
|
|
6620
6706
|
// working image model registered. Naming the reference syntax matters as
|
|
6621
6707
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
6622
|
-
// missing image.
|
|
6708
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
6709
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
6710
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
6711
|
+
// silently degrades to caption text.
|
|
6623
6712
|
//
|
|
6624
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
6625
|
-
//
|
|
6626
|
-
//
|
|
6627
|
-
//
|
|
6628
|
-
//
|
|
6629
|
-
has("generate_image") &&
|
|
6713
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
6714
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
6715
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
6716
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
6717
|
+
// ever sees.
|
|
6718
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
6719
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
6720
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
6721
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
6722
|
+
// blocks render as real chart objects.
|
|
6723
|
+
'- 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.',
|
|
6724
|
+
// The other half of image reliability, and the half no amount of
|
|
6725
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
6726
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
6727
|
+
// With neither, the model was never given a choice — and a model that
|
|
6728
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
6729
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
6730
|
+
!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.",
|
|
6630
6731
|
// The video counterpart of the image rule above, and the fix for the
|
|
6631
6732
|
// reported "it writes scripts forever and never makes the video" run. The
|
|
6632
6733
|
// failure mode is identical — the model narrates the deliverable instead of
|
|
@@ -6636,7 +6737,7 @@ function buildWorkerRules(has) {
|
|
|
6636
6737
|
// the deliverable rather than an illustration inside one, and the reference
|
|
6637
6738
|
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
6638
6739
|
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.',
|
|
6639
|
-
has("run_code") &&
|
|
6740
|
+
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.`,
|
|
6640
6741
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
6641
6742
|
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.',
|
|
6642
6743
|
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.",
|
|
@@ -6647,13 +6748,34 @@ function buildWorkerRules(has) {
|
|
|
6647
6748
|
Rules:
|
|
6648
6749
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
6649
6750
|
}
|
|
6650
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
6751
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
6752
|
+
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;
|
|
6651
6753
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
6652
6754
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
6653
6755
|
if (assignment?.files?.length) return true;
|
|
6654
6756
|
const haystack = `${assignment?.description ?? ""}
|
|
6655
6757
|
${assignment?.expectedOutput ?? ""}`;
|
|
6656
|
-
return
|
|
6758
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6759
|
+
}
|
|
6760
|
+
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;
|
|
6761
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
6762
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
6763
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
6764
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
6765
|
+
if (!canVisualize) return null;
|
|
6766
|
+
const haystack = `${assignment?.description ?? ""}
|
|
6767
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
6768
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
6769
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
6770
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
6771
|
+
const options = [];
|
|
6772
|
+
if (toolNames.includes("generate_image")) {
|
|
6773
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
6774
|
+
}
|
|
6775
|
+
if (toolNames.includes("generate_document")) {
|
|
6776
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
6777
|
+
}
|
|
6778
|
+
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.`;
|
|
6657
6779
|
}
|
|
6658
6780
|
var T3Worker = class extends BaseTier {
|
|
6659
6781
|
router;
|
|
@@ -6823,7 +6945,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
6823
6945
|
}
|
|
6824
6946
|
const artifactCheck = mustProduceArtifact ? await this.verifyArtifacts(assignment) : { ok: true, issues: [] };
|
|
6825
6947
|
if (!artifactCheck.ok) {
|
|
6826
|
-
correctionAttempts
|
|
6948
|
+
correctionAttempts++;
|
|
6827
6949
|
issues.push(...artifactCheck.issues);
|
|
6828
6950
|
output = await this.correctOutput(output, artifactCheck.issues);
|
|
6829
6951
|
const retryArtifactCheck = await this.verifyArtifacts(assignment);
|
|
@@ -6834,13 +6956,47 @@ Now execute your subtask using this context where relevant.`
|
|
|
6834
6956
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6835
6957
|
}
|
|
6836
6958
|
}
|
|
6959
|
+
const visualIssue = missingVisualEvidence(
|
|
6960
|
+
assignment,
|
|
6961
|
+
output,
|
|
6962
|
+
toolCalls.map((t) => t.name),
|
|
6963
|
+
this.tools.map((t) => t.name)
|
|
6964
|
+
);
|
|
6965
|
+
if (visualIssue) {
|
|
6966
|
+
correctionAttempts++;
|
|
6967
|
+
issues.push(visualIssue);
|
|
6968
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6969
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
6970
|
+
}
|
|
6971
|
+
const acceptanceResults = await this.checkAcceptance(assignment);
|
|
6972
|
+
const acceptanceFailures = failures(acceptanceResults);
|
|
6973
|
+
if (acceptanceFailures.length > 0) {
|
|
6974
|
+
correctionAttempts++;
|
|
6975
|
+
issues.push(...acceptanceFailures);
|
|
6976
|
+
this.sendStatusUpdate({ progressPct: 69, currentAction: "Acceptance checks failed \u2014 correcting", status: "IN_PROGRESS" });
|
|
6977
|
+
output = await this.correctOutput(output, acceptanceFailures);
|
|
6978
|
+
const recheck = failures(await this.checkAcceptance(assignment));
|
|
6979
|
+
if (recheck.length > 0) {
|
|
6980
|
+
issues.push(...recheck);
|
|
6981
|
+
checksRun.push(...acceptanceResults.map((r) => r.criterion));
|
|
6982
|
+
failed.push(...recheck);
|
|
6983
|
+
this.setStatus("FAILED");
|
|
6984
|
+
this.peerBus?.publish(this.id, assignment.subtaskId, output, "ESCALATED");
|
|
6985
|
+
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
for (const result2 of acceptanceResults) {
|
|
6989
|
+
if (result2.verdict === "undecidable") continue;
|
|
6990
|
+
checksRun.push(result2.criterion);
|
|
6991
|
+
if (result2.verdict === "passed") passed.push(result2.criterion);
|
|
6992
|
+
}
|
|
6837
6993
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6838
|
-
const testResult = await this.selfTest(assignment, output);
|
|
6994
|
+
const testResult = await this.selfTest(assignment, output, undecided(acceptanceResults));
|
|
6839
6995
|
checksRun.push(...testResult.checksRun);
|
|
6840
6996
|
passed.push(...testResult.passed);
|
|
6841
6997
|
failed.push(...testResult.failed);
|
|
6842
6998
|
if (testResult.failed.length > 0) {
|
|
6843
|
-
correctionAttempts
|
|
6999
|
+
correctionAttempts++;
|
|
6844
7000
|
issues.push(`Initial check failed: ${testResult.failed.join(", ")}`);
|
|
6845
7001
|
const corrected = await this.correctOutput(output, testResult.failed);
|
|
6846
7002
|
output = corrected;
|
|
@@ -7148,8 +7304,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7148
7304
|
// Media generation can run for a minute; without this a cancelled run
|
|
7149
7305
|
// still pays for an image nobody will see.
|
|
7150
7306
|
...this.signal ? { signal: this.signal } : {},
|
|
7151
|
-
saveSnapshot: async (
|
|
7152
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7307
|
+
saveSnapshot: async (path28, content) => {
|
|
7308
|
+
this.store?.addFileSnapshot(this.taskId, path28, content);
|
|
7153
7309
|
},
|
|
7154
7310
|
sendPeerSync: (to, syncType, content) => {
|
|
7155
7311
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7325,7 +7481,7 @@ ${assignment.expectedOutput}`;
|
|
|
7325
7481
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7326
7482
|
const haystack = `${assignment.description}
|
|
7327
7483
|
${assignment.expectedOutput}`;
|
|
7328
|
-
const matches = haystack.match(
|
|
7484
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
7329
7485
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
7330
7486
|
}
|
|
7331
7487
|
async verifyArtifacts(assignment) {
|
|
@@ -7336,9 +7492,9 @@ ${assignment.expectedOutput}`;
|
|
|
7336
7492
|
const { promisify: promisify4 } = await import('util');
|
|
7337
7493
|
const execAsync2 = promisify4(exec2);
|
|
7338
7494
|
for (const artifactPath of artifactPaths) {
|
|
7339
|
-
const absolutePath =
|
|
7495
|
+
const absolutePath = path26.resolve(process.cwd(), artifactPath);
|
|
7340
7496
|
try {
|
|
7341
|
-
const stat = await
|
|
7497
|
+
const stat = await fs5.stat(absolutePath);
|
|
7342
7498
|
if (!stat.isFile()) {
|
|
7343
7499
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
7344
7500
|
continue;
|
|
@@ -7347,8 +7503,22 @@ ${assignment.expectedOutput}`;
|
|
|
7347
7503
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
7348
7504
|
continue;
|
|
7349
7505
|
}
|
|
7350
|
-
if (
|
|
7351
|
-
const
|
|
7506
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
7507
|
+
const head = Buffer.alloc(4);
|
|
7508
|
+
const fh = await fs5.open(absolutePath, "r");
|
|
7509
|
+
try {
|
|
7510
|
+
await fh.read(head, 0, 4, 0);
|
|
7511
|
+
} finally {
|
|
7512
|
+
await fh.close();
|
|
7513
|
+
}
|
|
7514
|
+
if (head.toString("latin1") !== "PK") {
|
|
7515
|
+
issues.push(
|
|
7516
|
+
`${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.`
|
|
7517
|
+
);
|
|
7518
|
+
continue;
|
|
7519
|
+
}
|
|
7520
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
7521
|
+
const content = await fs5.readFile(absolutePath, "utf-8");
|
|
7352
7522
|
if (!content.trim()) {
|
|
7353
7523
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
7354
7524
|
continue;
|
|
@@ -7357,7 +7527,7 @@ ${assignment.expectedOutput}`;
|
|
|
7357
7527
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7358
7528
|
continue;
|
|
7359
7529
|
}
|
|
7360
|
-
const ext =
|
|
7530
|
+
const ext = path26.extname(absolutePath).toLowerCase();
|
|
7361
7531
|
try {
|
|
7362
7532
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7363
7533
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7443,14 +7613,42 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7443
7613
|
}
|
|
7444
7614
|
return current;
|
|
7445
7615
|
}
|
|
7446
|
-
|
|
7616
|
+
/**
|
|
7617
|
+
* Run the acceptance criteria that can be settled by looking at the workspace.
|
|
7618
|
+
* Reads go through the same workspace-relative resolution verifyArtifacts uses.
|
|
7619
|
+
*/
|
|
7620
|
+
async checkAcceptance(assignment) {
|
|
7621
|
+
const criteria = assignment.acceptance ?? [];
|
|
7622
|
+
if (!criteria.length) return [];
|
|
7623
|
+
const resolve2 = (target) => path26.resolve(process.cwd(), target);
|
|
7624
|
+
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7625
|
+
stat: async (target) => {
|
|
7626
|
+
try {
|
|
7627
|
+
const stat = await fs5.stat(resolve2(target));
|
|
7628
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
7629
|
+
} catch {
|
|
7630
|
+
return null;
|
|
7631
|
+
}
|
|
7632
|
+
},
|
|
7633
|
+
read: async (target) => {
|
|
7634
|
+
try {
|
|
7635
|
+
const content = await fs5.readFile(resolve2(target), "utf-8");
|
|
7636
|
+
return content.includes("\uFFFD") ? null : content;
|
|
7637
|
+
} catch {
|
|
7638
|
+
return null;
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
});
|
|
7642
|
+
}
|
|
7643
|
+
async selfTest(assignment, output, pendingAcceptance) {
|
|
7644
|
+
const acceptance = pendingAcceptance ?? assignment.acceptance ?? [];
|
|
7447
7645
|
const prompt = `Self-test this output against the assignment requirements.
|
|
7448
7646
|
|
|
7449
7647
|
Assignment: ${assignment.description}
|
|
7450
7648
|
Expected output: ${assignment.expectedOutput}
|
|
7451
7649
|
Constraints: ${assignment.constraints.join("; ")}
|
|
7452
|
-
${
|
|
7453
|
-
${
|
|
7650
|
+
${acceptance.length ? `Acceptance criteria \u2014 ALL must be satisfied for "completeness" to pass:
|
|
7651
|
+
${acceptance.map((a) => `- ${a}`).join("\n")}
|
|
7454
7652
|
` : ""}
|
|
7455
7653
|
Output to test:
|
|
7456
7654
|
${output}
|
|
@@ -7517,8 +7715,8 @@ ${output.slice(0, 4e3)}`;
|
|
|
7517
7715
|
} catch {
|
|
7518
7716
|
}
|
|
7519
7717
|
}
|
|
7520
|
-
async correctOutput(originalOutput,
|
|
7521
|
-
const correctionPrompt = `The following output failed these checks: ${
|
|
7718
|
+
async correctOutput(originalOutput, failures2) {
|
|
7719
|
+
const correctionPrompt = `The following output failed these checks: ${failures2.join(", ")}.
|
|
7522
7720
|
|
|
7523
7721
|
Original output:
|
|
7524
7722
|
${originalOutput}
|
|
@@ -8179,12 +8377,220 @@ function describeGenerationForPlanner(has) {
|
|
|
8179
8377
|
return lines.join("\n");
|
|
8180
8378
|
}
|
|
8181
8379
|
|
|
8380
|
+
// src/core/orchestration/task-graph.ts
|
|
8381
|
+
var TaskGraphValidationError = class extends Error {
|
|
8382
|
+
constructor(issues) {
|
|
8383
|
+
super(issues.map((issue) => issue.message).join("; "));
|
|
8384
|
+
this.issues = issues;
|
|
8385
|
+
this.name = "TaskGraphValidationError";
|
|
8386
|
+
}
|
|
8387
|
+
issues;
|
|
8388
|
+
};
|
|
8389
|
+
function normalizeDependencies(input) {
|
|
8390
|
+
if (!input?.length) return [];
|
|
8391
|
+
return [...new Set(input.map((dependency) => dependency.trim()).filter(Boolean))];
|
|
8392
|
+
}
|
|
8393
|
+
function cycleNodes(nodes) {
|
|
8394
|
+
const adjacency = new Map(
|
|
8395
|
+
nodes.map((node) => [node.id, node.dependsOn])
|
|
8396
|
+
);
|
|
8397
|
+
const index = /* @__PURE__ */ new Map();
|
|
8398
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
8399
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
8400
|
+
const stack = [];
|
|
8401
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
8402
|
+
let counter = 0;
|
|
8403
|
+
const open = (id) => {
|
|
8404
|
+
index.set(id, counter);
|
|
8405
|
+
lowlink.set(id, counter);
|
|
8406
|
+
counter++;
|
|
8407
|
+
stack.push(id);
|
|
8408
|
+
onStack.add(id);
|
|
8409
|
+
};
|
|
8410
|
+
for (const root of nodes) {
|
|
8411
|
+
if (index.has(root.id)) continue;
|
|
8412
|
+
open(root.id);
|
|
8413
|
+
const frames = [{ id: root.id, edge: 0 }];
|
|
8414
|
+
while (frames.length > 0) {
|
|
8415
|
+
const frame = frames[frames.length - 1];
|
|
8416
|
+
const neighbours = adjacency.get(frame.id) ?? [];
|
|
8417
|
+
if (frame.edge < neighbours.length) {
|
|
8418
|
+
const next = neighbours[frame.edge];
|
|
8419
|
+
frame.edge++;
|
|
8420
|
+
if (!adjacency.has(next)) continue;
|
|
8421
|
+
if (!index.has(next)) {
|
|
8422
|
+
open(next);
|
|
8423
|
+
frames.push({ id: next, edge: 0 });
|
|
8424
|
+
} else if (onStack.has(next)) {
|
|
8425
|
+
lowlink.set(frame.id, Math.min(lowlink.get(frame.id), index.get(next)));
|
|
8426
|
+
}
|
|
8427
|
+
continue;
|
|
8428
|
+
}
|
|
8429
|
+
frames.pop();
|
|
8430
|
+
const parent = frames[frames.length - 1];
|
|
8431
|
+
if (parent) {
|
|
8432
|
+
lowlink.set(parent.id, Math.min(lowlink.get(parent.id), lowlink.get(frame.id)));
|
|
8433
|
+
}
|
|
8434
|
+
if (lowlink.get(frame.id) === index.get(frame.id)) {
|
|
8435
|
+
const component = [];
|
|
8436
|
+
for (; ; ) {
|
|
8437
|
+
const member = stack.pop();
|
|
8438
|
+
onStack.delete(member);
|
|
8439
|
+
component.push(member);
|
|
8440
|
+
if (member === frame.id) break;
|
|
8441
|
+
}
|
|
8442
|
+
const cyclic = component.length > 1 || (adjacency.get(component[0]) ?? []).includes(component[0]);
|
|
8443
|
+
if (cyclic) for (const member of component) inCycle.add(member);
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
return inCycle;
|
|
8448
|
+
}
|
|
8449
|
+
function throwIfStrict(mode, issues) {
|
|
8450
|
+
if (mode === "strict" && issues.length > 0) {
|
|
8451
|
+
throw new TaskGraphValidationError(issues);
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
function compileTaskGraph(inputNodes, options = {}) {
|
|
8455
|
+
const mode = options.mode ?? "strict";
|
|
8456
|
+
const issues = [];
|
|
8457
|
+
if (inputNodes.length === 0) {
|
|
8458
|
+
issues.push({ code: "INVALID_NODE", message: "Task graph requires at least one node" });
|
|
8459
|
+
throwIfStrict(mode, issues);
|
|
8460
|
+
}
|
|
8461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8462
|
+
const nodes = [];
|
|
8463
|
+
for (let ordinal = 0; ordinal < inputNodes.length; ordinal++) {
|
|
8464
|
+
const input = inputNodes[ordinal];
|
|
8465
|
+
const id = input.id?.trim();
|
|
8466
|
+
const title = input.title?.trim();
|
|
8467
|
+
if (!id || !title) {
|
|
8468
|
+
issues.push({
|
|
8469
|
+
code: "INVALID_NODE",
|
|
8470
|
+
nodeId: id || void 0,
|
|
8471
|
+
message: `Task graph node at index ${ordinal} requires a non-empty id and title`
|
|
8472
|
+
});
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
if (seen.has(id)) {
|
|
8476
|
+
issues.push({
|
|
8477
|
+
code: "DUPLICATE_NODE_ID",
|
|
8478
|
+
nodeId: id,
|
|
8479
|
+
message: `Duplicate task graph node id: ${id}`
|
|
8480
|
+
});
|
|
8481
|
+
continue;
|
|
8482
|
+
}
|
|
8483
|
+
seen.add(id);
|
|
8484
|
+
nodes.push({
|
|
8485
|
+
id,
|
|
8486
|
+
title,
|
|
8487
|
+
kind: input.kind,
|
|
8488
|
+
dependsOn: normalizeDependencies(input.dependsOn),
|
|
8489
|
+
payload: input.payload,
|
|
8490
|
+
ordinal
|
|
8491
|
+
});
|
|
8492
|
+
}
|
|
8493
|
+
throwIfStrict(mode, issues);
|
|
8494
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
8495
|
+
const sanitized = nodes.map((node) => {
|
|
8496
|
+
const dependsOn = [];
|
|
8497
|
+
for (const dependencyId of node.dependsOn) {
|
|
8498
|
+
if (dependencyId === node.id) {
|
|
8499
|
+
issues.push({
|
|
8500
|
+
code: "SELF_DEPENDENCY",
|
|
8501
|
+
nodeId: node.id,
|
|
8502
|
+
dependencyId,
|
|
8503
|
+
message: `Task graph node ${node.id} cannot depend on itself`
|
|
8504
|
+
});
|
|
8505
|
+
continue;
|
|
8506
|
+
}
|
|
8507
|
+
if (!ids.has(dependencyId)) {
|
|
8508
|
+
issues.push({
|
|
8509
|
+
code: "UNKNOWN_DEPENDENCY",
|
|
8510
|
+
nodeId: node.id,
|
|
8511
|
+
dependencyId,
|
|
8512
|
+
message: `Task graph node ${node.id} depends on unknown node ${dependencyId}`
|
|
8513
|
+
});
|
|
8514
|
+
continue;
|
|
8515
|
+
}
|
|
8516
|
+
dependsOn.push(dependencyId);
|
|
8517
|
+
}
|
|
8518
|
+
return { ...node, dependsOn };
|
|
8519
|
+
});
|
|
8520
|
+
throwIfStrict(mode, issues);
|
|
8521
|
+
const cyclic = cycleNodes(sanitized);
|
|
8522
|
+
let repaired = sanitized;
|
|
8523
|
+
if (cyclic.size > 0) {
|
|
8524
|
+
const cycleIssues = [];
|
|
8525
|
+
repaired = sanitized.map((node) => {
|
|
8526
|
+
if (!cyclic.has(node.id)) return node;
|
|
8527
|
+
const dependsOn = node.dependsOn.filter((dependencyId) => {
|
|
8528
|
+
if (!cyclic.has(dependencyId)) return true;
|
|
8529
|
+
cycleIssues.push({
|
|
8530
|
+
code: "CYCLE_EDGE_REMOVED",
|
|
8531
|
+
nodeId: node.id,
|
|
8532
|
+
dependencyId,
|
|
8533
|
+
message: `Removed cyclic dependency ${dependencyId} -> ${node.id}`
|
|
8534
|
+
});
|
|
8535
|
+
return false;
|
|
8536
|
+
});
|
|
8537
|
+
return { ...node, dependsOn };
|
|
8538
|
+
});
|
|
8539
|
+
issues.push(...cycleIssues);
|
|
8540
|
+
throwIfStrict(mode, cycleIssues);
|
|
8541
|
+
}
|
|
8542
|
+
const remainingCycle = cycleNodes(repaired);
|
|
8543
|
+
if (remainingCycle.size > 0) {
|
|
8544
|
+
throw new TaskGraphValidationError([
|
|
8545
|
+
{
|
|
8546
|
+
code: "INVALID_NODE",
|
|
8547
|
+
message: `Task graph remained cyclic after repair: ${[...remainingCycle].join(", ")}`
|
|
8548
|
+
}
|
|
8549
|
+
]);
|
|
8550
|
+
}
|
|
8551
|
+
return {
|
|
8552
|
+
graph: { nodes: repaired.sort((a, b) => a.ordinal - b.ordinal) },
|
|
8553
|
+
issues
|
|
8554
|
+
};
|
|
8555
|
+
}
|
|
8556
|
+
|
|
8557
|
+
// src/core/orchestration/adapters.ts
|
|
8558
|
+
function compileSectionGraph(sections) {
|
|
8559
|
+
return compileTaskGraph(
|
|
8560
|
+
sections.map((section) => ({
|
|
8561
|
+
id: section.sectionId,
|
|
8562
|
+
title: section.sectionTitle,
|
|
8563
|
+
kind: "section",
|
|
8564
|
+
dependsOn: section.dependsOn,
|
|
8565
|
+
payload: section
|
|
8566
|
+
})),
|
|
8567
|
+
{ mode: "repair" }
|
|
8568
|
+
);
|
|
8569
|
+
}
|
|
8570
|
+
function compileSubtaskGraph(subtasks) {
|
|
8571
|
+
return compileTaskGraph(
|
|
8572
|
+
subtasks.map((subtask) => ({
|
|
8573
|
+
id: subtask.subtaskId,
|
|
8574
|
+
title: subtask.subtaskTitle,
|
|
8575
|
+
kind: "subtask",
|
|
8576
|
+
dependsOn: subtask.dependsOn,
|
|
8577
|
+
payload: subtask
|
|
8578
|
+
})),
|
|
8579
|
+
{ mode: "repair" }
|
|
8580
|
+
);
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8182
8583
|
// src/core/tiers/t2-manager.ts
|
|
8183
8584
|
function buildT2SystemPrompt(has) {
|
|
8184
8585
|
const generation = describeGenerationForPlanner(has);
|
|
8185
8586
|
return [
|
|
8186
8587
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
8187
|
-
|
|
8588
|
+
// Must agree with the decomposition prompt in decomposeSection(), which is
|
|
8589
|
+
// the one carrying the actual instruction. It said 1-4 and "the FEWEST that
|
|
8590
|
+
// fully cover it"; this said 2-5. The model received both, so its floor was
|
|
8591
|
+
// simultaneously one and two, and the cheaper reading — pad to two — costs a
|
|
8592
|
+
// real worker call on every small section.
|
|
8593
|
+
"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.",
|
|
8188
8594
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
8189
8595
|
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8190
8596
|
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
@@ -8580,21 +8986,30 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8580
8986
|
* then reduce in-degrees of their dependents and repeat.
|
|
8581
8987
|
*/
|
|
8582
8988
|
async runWithDependencies(assignments, workerMap, taskId) {
|
|
8989
|
+
const compiled = compileSubtaskGraph(assignments);
|
|
8990
|
+
for (const issue of compiled.issues) {
|
|
8991
|
+
this.log(`\u26A0 Subtask graph: ${issue.message}`);
|
|
8992
|
+
}
|
|
8993
|
+
const sanitizedAssignments = compiled.graph.nodes.map((node) => {
|
|
8994
|
+
const assignment = node.payload;
|
|
8995
|
+
const original = assignment.dependsOn ?? [];
|
|
8996
|
+
const repaired = node.dependsOn;
|
|
8997
|
+
const unchanged = repaired.length === original.length && repaired.every((dependencyId, index) => dependencyId === original[index]);
|
|
8998
|
+
return unchanged ? assignment : { ...assignment, dependsOn: [...repaired] };
|
|
8999
|
+
});
|
|
8583
9000
|
const adj = /* @__PURE__ */ new Map();
|
|
8584
9001
|
const inDegree = /* @__PURE__ */ new Map();
|
|
8585
9002
|
const resultMap = /* @__PURE__ */ new Map();
|
|
8586
|
-
for (const a of
|
|
9003
|
+
for (const a of sanitizedAssignments) {
|
|
8587
9004
|
if (!adj.has(a.subtaskId)) adj.set(a.subtaskId, /* @__PURE__ */ new Set());
|
|
8588
9005
|
inDegree.set(a.subtaskId, 0);
|
|
8589
9006
|
}
|
|
8590
|
-
for (const a of
|
|
8591
|
-
const
|
|
8592
|
-
for (const dep of deps) {
|
|
9007
|
+
for (const a of sanitizedAssignments) {
|
|
9008
|
+
for (const dep of a.dependsOn ?? []) {
|
|
8593
9009
|
adj.get(dep).add(a.subtaskId);
|
|
8594
9010
|
inDegree.set(a.subtaskId, (inDegree.get(a.subtaskId) ?? 0) + 1);
|
|
8595
9011
|
}
|
|
8596
9012
|
}
|
|
8597
|
-
const sanitizedAssignments = this.breakCycles(assignments, adj, inDegree);
|
|
8598
9013
|
let remaining = new Set(sanitizedAssignments.map((a) => a.subtaskId));
|
|
8599
9014
|
let wave = 0;
|
|
8600
9015
|
let respawnBudget = 1;
|
|
@@ -8801,49 +9216,6 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8801
9216
|
}
|
|
8802
9217
|
return [...resultMap.values()];
|
|
8803
9218
|
}
|
|
8804
|
-
/**
|
|
8805
|
-
* Detects cyclic dependencies using Kahn's algorithm and breaks them
|
|
8806
|
-
* by removing back-edges. Returns a sanitized copy of assignments.
|
|
8807
|
-
*
|
|
8808
|
-
* A cycle like t1→t2→t3→t1 is broken at the last edge (t3→t1),
|
|
8809
|
-
* meaning t3 will start without waiting for t1, preventing deadlock.
|
|
8810
|
-
*/
|
|
8811
|
-
breakCycles(assignments, adj, inDegree) {
|
|
8812
|
-
const degree = new Map(inDegree);
|
|
8813
|
-
const queue = [];
|
|
8814
|
-
const visited = /* @__PURE__ */ new Set();
|
|
8815
|
-
for (const [id, d] of degree) {
|
|
8816
|
-
if (d === 0) queue.push(id);
|
|
8817
|
-
}
|
|
8818
|
-
while (queue.length > 0) {
|
|
8819
|
-
const id = queue.shift();
|
|
8820
|
-
visited.add(id);
|
|
8821
|
-
for (const dep of adj.get(id) ?? []) {
|
|
8822
|
-
const newDeg = (degree.get(dep) ?? 1) - 1;
|
|
8823
|
-
degree.set(dep, newDeg);
|
|
8824
|
-
if (newDeg === 0) queue.push(dep);
|
|
8825
|
-
}
|
|
8826
|
-
}
|
|
8827
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
8828
|
-
if (cycleNodes.length === 0) return assignments;
|
|
8829
|
-
this.log(
|
|
8830
|
-
`\u26A0 Circular dependency detected among subtasks: [${cycleNodes.join(", ")}]. Breaking cycles \u2014 affected tasks will run without their cyclic dependencies.`
|
|
8831
|
-
);
|
|
8832
|
-
return assignments.map((a) => {
|
|
8833
|
-
if (!cycleNodes.includes(a.subtaskId)) return a;
|
|
8834
|
-
const safeDeps = (a.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
8835
|
-
if (safeDeps.length !== (a.dependsOn ?? []).length) {
|
|
8836
|
-
this.log(
|
|
8837
|
-
` \u2192 Breaking cycle: removed ${(a.dependsOn ?? []).filter((d) => cycleNodes.includes(d)).join(", ")} from "${a.subtaskTitle}" dependsOn`
|
|
8838
|
-
);
|
|
8839
|
-
for (const removed of (a.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
8840
|
-
inDegree.set(a.subtaskId, Math.max(0, (inDegree.get(a.subtaskId) ?? 1) - 1));
|
|
8841
|
-
adj.get(removed)?.delete(a.subtaskId);
|
|
8842
|
-
}
|
|
8843
|
-
}
|
|
8844
|
-
return { ...a, dependsOn: safeDeps };
|
|
8845
|
-
});
|
|
8846
|
-
}
|
|
8847
9219
|
async retryT3(assignment, taskId) {
|
|
8848
9220
|
this.log(`Retrying T3 for subtask: ${assignment.subtaskTitle}`);
|
|
8849
9221
|
const worker = new T3Worker(this.router, this.toolRegistry, this.id);
|
|
@@ -8979,6 +9351,57 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8979
9351
|
}
|
|
8980
9352
|
};
|
|
8981
9353
|
|
|
9354
|
+
// src/core/orchestration/scheduler.ts
|
|
9355
|
+
var DependencyScheduler = class {
|
|
9356
|
+
constructor(graph, hooks, options = {}) {
|
|
9357
|
+
this.graph = graph;
|
|
9358
|
+
this.hooks = hooks;
|
|
9359
|
+
this.options = options;
|
|
9360
|
+
}
|
|
9361
|
+
graph;
|
|
9362
|
+
hooks;
|
|
9363
|
+
options;
|
|
9364
|
+
async run() {
|
|
9365
|
+
const nodes = [...this.graph.nodes].sort((a, b) => a.ordinal - b.ordinal);
|
|
9366
|
+
const inDegree = new Map(nodes.map((node) => [node.id, node.dependsOn.length]));
|
|
9367
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
9368
|
+
for (const node of nodes) dependents.set(node.id, []);
|
|
9369
|
+
for (const node of nodes) {
|
|
9370
|
+
for (const dependencyId of node.dependsOn) {
|
|
9371
|
+
dependents.get(dependencyId)?.push(node.id);
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
const completed = /* @__PURE__ */ new Set();
|
|
9375
|
+
const results = /* @__PURE__ */ new Map();
|
|
9376
|
+
const waves = [];
|
|
9377
|
+
let wave = 0;
|
|
9378
|
+
while (completed.size < nodes.length) {
|
|
9379
|
+
const ready = nodes.filter((node) => !completed.has(node.id) && (inDegree.get(node.id) ?? 0) === 0);
|
|
9380
|
+
if (ready.length === 0) {
|
|
9381
|
+
throw new Error("Dependency scheduler received a cyclic or incomplete task graph");
|
|
9382
|
+
}
|
|
9383
|
+
waves.push(ready.map((node) => node.id));
|
|
9384
|
+
await this.hooks.onWaveStart?.(ready, wave);
|
|
9385
|
+
const runOne = async (node) => {
|
|
9386
|
+
const result = await this.hooks.execute(node);
|
|
9387
|
+
results.set(node.id, result);
|
|
9388
|
+
completed.add(node.id);
|
|
9389
|
+
for (const dependentId of dependents.get(node.id) ?? []) {
|
|
9390
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9391
|
+
}
|
|
9392
|
+
await this.hooks.onNodeComplete?.(node, result);
|
|
9393
|
+
};
|
|
9394
|
+
if ((this.options.mode ?? "parallel") === "sequential") {
|
|
9395
|
+
for (const node of ready) await runOne(node);
|
|
9396
|
+
} else {
|
|
9397
|
+
await Promise.all(ready.map(runOne));
|
|
9398
|
+
}
|
|
9399
|
+
wave++;
|
|
9400
|
+
}
|
|
9401
|
+
return { results, waves };
|
|
9402
|
+
}
|
|
9403
|
+
};
|
|
9404
|
+
|
|
8982
9405
|
// src/utils/json-extract.ts
|
|
8983
9406
|
function extractFirstJsonObject(input) {
|
|
8984
9407
|
if (!input) return null;
|
|
@@ -9050,7 +9473,7 @@ function buildT1SystemPrompt(has) {
|
|
|
9050
9473
|
Your responsibilities:
|
|
9051
9474
|
1. Analyze task complexity: Simple | Moderate | Complex | Highly Complex
|
|
9052
9475
|
2. Decompose the task into logical sections (one per T2 Manager)
|
|
9053
|
-
3. For each section, define
|
|
9476
|
+
3. For each section, define 1-4 subtasks for T3 Workers \u2014 the fewest that fully cover it
|
|
9054
9477
|
4. Return a structured plan as JSON
|
|
9055
9478
|
|
|
9056
9479
|
CRITICAL PATH RULE: If the user specifies a target directory (e.g. "inside python_exclusive",
|
|
@@ -9465,7 +9888,11 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9465
9888
|
}
|
|
9466
9889
|
}
|
|
9467
9890
|
const [min, max] = COMPLEXITY_T2_COUNT[plan.complexity] ?? [1, 8];
|
|
9468
|
-
if (plan.sections.length < min)
|
|
9891
|
+
if (plan.sections.length < min || plan.sections.length > max) {
|
|
9892
|
+
this.log(
|
|
9893
|
+
`\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.`
|
|
9894
|
+
);
|
|
9895
|
+
}
|
|
9469
9896
|
}
|
|
9470
9897
|
async dispatchT2Managers(sections) {
|
|
9471
9898
|
for (const section of sections) {
|
|
@@ -9577,114 +10004,91 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
9577
10004
|
return t2Results;
|
|
9578
10005
|
}
|
|
9579
10006
|
/**
|
|
9580
|
-
* Runs T2 managers respecting dependsOn declarations
|
|
10007
|
+
* Runs T2 managers respecting dependsOn declarations.
|
|
10008
|
+
*
|
|
10009
|
+
* Graph construction, validation and cycle repair now live in
|
|
10010
|
+
* compileSectionGraph (orchestration/adapters.ts), and wave execution in
|
|
10011
|
+
* DependencyScheduler — the same pair T2 uses for its subtasks, so a plan is
|
|
10012
|
+
* ordered by one implementation rather than two that drifted.
|
|
10013
|
+
*
|
|
10014
|
+
* The hand-rolled Kahn pass this replaces treated every node it could not
|
|
10015
|
+
* reach as part of a cycle. That set also contains everything DOWNSTREAM of a
|
|
10016
|
+
* cycle, whose in-degree can never fall to zero while its dependency is stuck,
|
|
10017
|
+
* so breaking a cycle here also cut the dependencies of innocent later
|
|
10018
|
+
* sections and started them immediately — before the sections they consume.
|
|
10019
|
+
* compileTaskGraph uses strongly connected components, so only real cycle
|
|
10020
|
+
* members are touched.
|
|
9581
10021
|
*/
|
|
9582
10022
|
async runT2sWithDependencies(sections, managers, taskId) {
|
|
9583
|
-
const
|
|
9584
|
-
const
|
|
9585
|
-
|
|
9586
|
-
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
9587
|
-
for (const s of sections) {
|
|
9588
|
-
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
9589
|
-
inDegree.set(s.sectionId, 0);
|
|
9590
|
-
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
9591
|
-
}
|
|
9592
|
-
for (const s of sections) {
|
|
9593
|
-
for (const dep of s.dependsOn ?? []) {
|
|
9594
|
-
adj.get(dep).add(s.sectionId);
|
|
9595
|
-
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
9596
|
-
}
|
|
10023
|
+
const compiled = compileSectionGraph(sections);
|
|
10024
|
+
for (const issue of compiled.issues) {
|
|
10025
|
+
this.log(`\u26A0 Section graph: ${issue.message}`);
|
|
9597
10026
|
}
|
|
9598
|
-
const
|
|
9599
|
-
const
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
while (queue.length > 0) {
|
|
9603
|
-
const u = queue.shift();
|
|
9604
|
-
visited.add(u);
|
|
9605
|
-
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
9606
|
-
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
9607
|
-
degree.set(v, newDeg);
|
|
9608
|
-
if (newDeg === 0) queue.push(v);
|
|
9609
|
-
}
|
|
9610
|
-
}
|
|
9611
|
-
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
9612
|
-
if (cycleNodes.length > 0) {
|
|
9613
|
-
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
9614
|
-
for (const s of sections) {
|
|
9615
|
-
if (cycleNodes.includes(s.sectionId)) {
|
|
9616
|
-
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
9617
|
-
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
9618
|
-
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
9619
|
-
adj.get(removed)?.delete(s.sectionId);
|
|
9620
|
-
}
|
|
9621
|
-
s.dependsOn = safeDeps;
|
|
9622
|
-
}
|
|
9623
|
-
}
|
|
10027
|
+
const byId = new Map(compiled.graph.nodes.map((node) => [node.id, node]));
|
|
10028
|
+
for (const section of sections) {
|
|
10029
|
+
const node = byId.get(section.sectionId);
|
|
10030
|
+
if (node) section.dependsOn = [...node.dependsOn];
|
|
9624
10031
|
}
|
|
9625
|
-
const
|
|
10032
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
10033
|
+
const totalSections = compiled.graph.nodes.length;
|
|
9626
10034
|
let completedSections = 0;
|
|
9627
|
-
const
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
10035
|
+
const scheduler = new DependencyScheduler(
|
|
10036
|
+
compiled.graph,
|
|
10037
|
+
{
|
|
10038
|
+
execute: async (node) => {
|
|
10039
|
+
const section = node.payload;
|
|
10040
|
+
const manager = managers[node.ordinal];
|
|
10041
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
10042
|
+
this.sendStatusUpdate({
|
|
10043
|
+
progressPct,
|
|
10044
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
10045
|
+
status: "IN_PROGRESS"
|
|
10046
|
+
});
|
|
10047
|
+
this.throwIfCancelled();
|
|
10048
|
+
try {
|
|
10049
|
+
const result = await manager.execute(section, taskId, this.signal);
|
|
10050
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
10051
|
+
if (result.status === "ESCALATED") {
|
|
10052
|
+
this.escalations.push({
|
|
10053
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
10054
|
+
sectionId: section.sectionId,
|
|
10055
|
+
attempted: result.issues,
|
|
10056
|
+
blocker: result.issues.join("; "),
|
|
10057
|
+
needs: "Human review required"
|
|
10058
|
+
});
|
|
10059
|
+
}
|
|
10060
|
+
return result;
|
|
10061
|
+
} catch (err) {
|
|
10062
|
+
return {
|
|
9654
10063
|
sectionId: section.sectionId,
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
10064
|
+
sectionTitle: section.sectionTitle,
|
|
10065
|
+
status: "FAILED",
|
|
10066
|
+
t3Results: [],
|
|
10067
|
+
sectionSummary: "",
|
|
10068
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
10069
|
+
};
|
|
9659
10070
|
}
|
|
9660
|
-
}
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
resultMap.set(id, result);
|
|
9671
|
-
completedSections++;
|
|
9672
|
-
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
9673
|
-
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
9674
|
-
}
|
|
9675
|
-
};
|
|
9676
|
-
if (this.router.getT3ExecutionMode?.() === "sequential") {
|
|
9677
|
-
for (const id of readyIds) {
|
|
9678
|
-
await runOne(id);
|
|
10071
|
+
},
|
|
10072
|
+
onNodeComplete: (node, result) => {
|
|
10073
|
+
resultMap.set(node.id, result);
|
|
10074
|
+
completedSections++;
|
|
10075
|
+
this.emit("section:complete", {
|
|
10076
|
+
id: node.id,
|
|
10077
|
+
title: result.sectionTitle,
|
|
10078
|
+
status: result.status,
|
|
10079
|
+
output: result.sectionSummary
|
|
10080
|
+
});
|
|
9679
10081
|
}
|
|
9680
|
-
}
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
10082
|
+
},
|
|
10083
|
+
{
|
|
10084
|
+
// Cross-section concurrency respects the same t3Execution mode as the
|
|
10085
|
+
// intra-section T3 wave (t2-manager.ts) — 'sequential' otherwise only
|
|
10086
|
+
// serialized T3 workers within a single section while every section's
|
|
10087
|
+
// T2Manager (and its T3 workers) still ran in parallel here.
|
|
10088
|
+
mode: this.router.getT3ExecutionMode?.() === "sequential" ? "sequential" : "parallel"
|
|
9685
10089
|
}
|
|
9686
|
-
|
|
9687
|
-
await
|
|
10090
|
+
);
|
|
10091
|
+
await scheduler.run();
|
|
9688
10092
|
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
9689
10093
|
}
|
|
9690
10094
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
@@ -10304,6 +10708,705 @@ function buildMediaTools(deps, readFile) {
|
|
|
10304
10708
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
10305
10709
|
return tools;
|
|
10306
10710
|
}
|
|
10711
|
+
var WorkspaceSandboxError = class extends Error {
|
|
10712
|
+
constructor(attempted, workspaceRoot) {
|
|
10713
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10714
|
+
this.name = "WorkspaceSandboxError";
|
|
10715
|
+
}
|
|
10716
|
+
};
|
|
10717
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
10718
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
10719
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10720
|
+
}
|
|
10721
|
+
const root = path26.resolve(workspaceRoot);
|
|
10722
|
+
const abs = path26.isAbsolute(input) ? path26.resolve(input) : path26.resolve(root, input);
|
|
10723
|
+
const rel = path26.relative(root, abs);
|
|
10724
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path26.isAbsolute(rel)) {
|
|
10725
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10726
|
+
}
|
|
10727
|
+
try {
|
|
10728
|
+
const real = fs25.realpathSync(abs);
|
|
10729
|
+
const realRel = path26.relative(root, real);
|
|
10730
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path26.isAbsolute(realRel))) {
|
|
10731
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10732
|
+
}
|
|
10733
|
+
} catch (e) {
|
|
10734
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10735
|
+
}
|
|
10736
|
+
return abs;
|
|
10737
|
+
}
|
|
10738
|
+
|
|
10739
|
+
// src/core/documents/blocks.ts
|
|
10740
|
+
function fileExt(name) {
|
|
10741
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
10742
|
+
const dot = base.lastIndexOf(".");
|
|
10743
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
10744
|
+
}
|
|
10745
|
+
function parseDelimited(text, name = "") {
|
|
10746
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
10747
|
+
const rows = [];
|
|
10748
|
+
let row = [];
|
|
10749
|
+
let cell = "";
|
|
10750
|
+
let quoted = false;
|
|
10751
|
+
for (let i = 0; i < text.length; i++) {
|
|
10752
|
+
const ch = text[i];
|
|
10753
|
+
if (quoted) {
|
|
10754
|
+
if (ch === '"') {
|
|
10755
|
+
if (text[i + 1] === '"') {
|
|
10756
|
+
cell += '"';
|
|
10757
|
+
i++;
|
|
10758
|
+
} else quoted = false;
|
|
10759
|
+
} else cell += ch;
|
|
10760
|
+
} else if (ch === '"') {
|
|
10761
|
+
quoted = true;
|
|
10762
|
+
} else if (ch === delim) {
|
|
10763
|
+
row.push(cell);
|
|
10764
|
+
cell = "";
|
|
10765
|
+
} else if (ch === "\n") {
|
|
10766
|
+
row.push(cell);
|
|
10767
|
+
rows.push(row);
|
|
10768
|
+
row = [];
|
|
10769
|
+
cell = "";
|
|
10770
|
+
} else if (ch === "\r") ; else {
|
|
10771
|
+
cell += ch;
|
|
10772
|
+
}
|
|
10773
|
+
if (rows.length > 5e3) break;
|
|
10774
|
+
}
|
|
10775
|
+
if (cell !== "" || row.length) {
|
|
10776
|
+
row.push(cell);
|
|
10777
|
+
rows.push(row);
|
|
10778
|
+
}
|
|
10779
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
10780
|
+
}
|
|
10781
|
+
var CHART_ALIASES = {
|
|
10782
|
+
bar: "bar",
|
|
10783
|
+
column: "bar",
|
|
10784
|
+
bars: "bar",
|
|
10785
|
+
barchart: "bar",
|
|
10786
|
+
horizontalbar: "bar",
|
|
10787
|
+
line: "line",
|
|
10788
|
+
lines: "line",
|
|
10789
|
+
linechart: "line",
|
|
10790
|
+
pie: "pie",
|
|
10791
|
+
piechart: "pie",
|
|
10792
|
+
doughnut: "doughnut",
|
|
10793
|
+
donut: "doughnut",
|
|
10794
|
+
area: "area",
|
|
10795
|
+
areachart: "area",
|
|
10796
|
+
scatter: "scatter",
|
|
10797
|
+
xy: "scatter"
|
|
10798
|
+
};
|
|
10799
|
+
function chartKind(raw) {
|
|
10800
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
10801
|
+
}
|
|
10802
|
+
function toNumber(cell) {
|
|
10803
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
10804
|
+
const n = Number.parseFloat(cleaned);
|
|
10805
|
+
return Number.isFinite(n) ? n : 0;
|
|
10806
|
+
}
|
|
10807
|
+
function parseChartSpec(kind, body) {
|
|
10808
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
10809
|
+
let title;
|
|
10810
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
10811
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
10812
|
+
if (titleMatch) {
|
|
10813
|
+
title = titleMatch[1].trim();
|
|
10814
|
+
lines.shift();
|
|
10815
|
+
}
|
|
10816
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
10817
|
+
if (rows.length < 2) return null;
|
|
10818
|
+
const header = rows[0].map((c) => c.trim());
|
|
10819
|
+
if (header.length < 2) return null;
|
|
10820
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
10821
|
+
if (!dataRows.length) return null;
|
|
10822
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
10823
|
+
const series = header.slice(1).map((name, col) => ({
|
|
10824
|
+
name: name || `Series ${col + 1}`,
|
|
10825
|
+
values: dataRows.map((r) => toNumber((r[col + 1] ?? "").trim()))
|
|
10826
|
+
}));
|
|
10827
|
+
if (!series.length) return null;
|
|
10828
|
+
return {
|
|
10829
|
+
kind,
|
|
10830
|
+
...title ? { title } : {},
|
|
10831
|
+
categoryName: header[0] || "Category",
|
|
10832
|
+
categories,
|
|
10833
|
+
series
|
|
10834
|
+
};
|
|
10835
|
+
}
|
|
10836
|
+
function chartToTableRows(spec) {
|
|
10837
|
+
return [
|
|
10838
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10839
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
10840
|
+
];
|
|
10841
|
+
}
|
|
10842
|
+
function parseBlocks(md) {
|
|
10843
|
+
const out = [];
|
|
10844
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
10845
|
+
let i = 0;
|
|
10846
|
+
let ordinal = 0;
|
|
10847
|
+
const reset = () => {
|
|
10848
|
+
ordinal = 0;
|
|
10849
|
+
};
|
|
10850
|
+
while (i < lines.length) {
|
|
10851
|
+
const line = lines[i] ?? "";
|
|
10852
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
10853
|
+
if (fence) {
|
|
10854
|
+
const info = (fence[1] ?? "").trim();
|
|
10855
|
+
i++;
|
|
10856
|
+
const code = [];
|
|
10857
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
10858
|
+
code.push(lines[i] ?? "");
|
|
10859
|
+
i++;
|
|
10860
|
+
}
|
|
10861
|
+
i++;
|
|
10862
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
10863
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
10864
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
10865
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
10866
|
+
reset();
|
|
10867
|
+
continue;
|
|
10868
|
+
}
|
|
10869
|
+
if (!line.trim()) {
|
|
10870
|
+
i++;
|
|
10871
|
+
reset();
|
|
10872
|
+
continue;
|
|
10873
|
+
}
|
|
10874
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
10875
|
+
if (h) {
|
|
10876
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
10877
|
+
i++;
|
|
10878
|
+
reset();
|
|
10879
|
+
continue;
|
|
10880
|
+
}
|
|
10881
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
10882
|
+
out.push({ t: "hr" });
|
|
10883
|
+
i++;
|
|
10884
|
+
reset();
|
|
10885
|
+
continue;
|
|
10886
|
+
}
|
|
10887
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
10888
|
+
if (li) {
|
|
10889
|
+
const ordered = /\d/.test(li[2]);
|
|
10890
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
10891
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
10892
|
+
i++;
|
|
10893
|
+
continue;
|
|
10894
|
+
}
|
|
10895
|
+
if (/^>\s?/.test(line)) {
|
|
10896
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
10897
|
+
i++;
|
|
10898
|
+
reset();
|
|
10899
|
+
continue;
|
|
10900
|
+
}
|
|
10901
|
+
if (/^\|.*\|/.test(line)) {
|
|
10902
|
+
const rows = [];
|
|
10903
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
10904
|
+
const r = lines[i];
|
|
10905
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
10906
|
+
i++;
|
|
10907
|
+
}
|
|
10908
|
+
out.push({ t: "table", rows });
|
|
10909
|
+
reset();
|
|
10910
|
+
continue;
|
|
10911
|
+
}
|
|
10912
|
+
out.push({ t: "para", text: line });
|
|
10913
|
+
i++;
|
|
10914
|
+
reset();
|
|
10915
|
+
}
|
|
10916
|
+
return out;
|
|
10917
|
+
}
|
|
10918
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
10919
|
+
function matchImageLine(s) {
|
|
10920
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
10921
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
10922
|
+
}
|
|
10923
|
+
function sniffImage(b) {
|
|
10924
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
10925
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
10926
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
10927
|
+
}
|
|
10928
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
10929
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
10930
|
+
}
|
|
10931
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
10932
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
10933
|
+
}
|
|
10934
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
10935
|
+
let i = 2;
|
|
10936
|
+
while (i + 9 < b.length) {
|
|
10937
|
+
if (b[i] !== 255) {
|
|
10938
|
+
i++;
|
|
10939
|
+
continue;
|
|
10940
|
+
}
|
|
10941
|
+
const marker = b[i + 1];
|
|
10942
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
10943
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
10944
|
+
}
|
|
10945
|
+
i += 2 + dv.getUint16(i + 2);
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
return null;
|
|
10949
|
+
}
|
|
10950
|
+
function bytesToBase64(bytes) {
|
|
10951
|
+
const g = globalThis;
|
|
10952
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
10953
|
+
const CHUNK = 32768;
|
|
10954
|
+
let binary = "";
|
|
10955
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
10956
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
10957
|
+
}
|
|
10958
|
+
return g.btoa(binary);
|
|
10959
|
+
}
|
|
10960
|
+
function stripInline(s) {
|
|
10961
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
10962
|
+
}
|
|
10963
|
+
function inlineRuns(text) {
|
|
10964
|
+
const acc = [];
|
|
10965
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
10966
|
+
let last = 0;
|
|
10967
|
+
let m;
|
|
10968
|
+
while ((m = re.exec(text)) !== null) {
|
|
10969
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
10970
|
+
const tok = m[0];
|
|
10971
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
10972
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
10973
|
+
else if (tok.startsWith("[")) {
|
|
10974
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
10975
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
10976
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
10977
|
+
last = re.lastIndex;
|
|
10978
|
+
}
|
|
10979
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
10980
|
+
return acc.length ? acc : [{ text }];
|
|
10981
|
+
}
|
|
10982
|
+
function splitSlides(md) {
|
|
10983
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
10984
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
10985
|
+
if (chunks.length === 1) {
|
|
10986
|
+
const lines = norm.split("\n");
|
|
10987
|
+
const sections = [];
|
|
10988
|
+
let cur = [];
|
|
10989
|
+
for (const ln of lines) {
|
|
10990
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
10991
|
+
sections.push(cur.join("\n"));
|
|
10992
|
+
cur = [];
|
|
10993
|
+
}
|
|
10994
|
+
cur.push(ln);
|
|
10995
|
+
}
|
|
10996
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
10997
|
+
if (sections.length > 1) chunks = sections;
|
|
10998
|
+
}
|
|
10999
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
11000
|
+
}
|
|
11001
|
+
function parseSlide(chunk) {
|
|
11002
|
+
let title = "";
|
|
11003
|
+
const body = [];
|
|
11004
|
+
const images = [];
|
|
11005
|
+
const charts = [];
|
|
11006
|
+
const lines = chunk.split("\n");
|
|
11007
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11008
|
+
const raw = lines[i] ?? "";
|
|
11009
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
11010
|
+
if (fence) {
|
|
11011
|
+
const kind = chartKind(fence[1]);
|
|
11012
|
+
const bodyLines = [];
|
|
11013
|
+
i++;
|
|
11014
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
11015
|
+
bodyLines.push(lines[i] ?? "");
|
|
11016
|
+
i++;
|
|
11017
|
+
}
|
|
11018
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
11019
|
+
if (spec) charts.push(spec);
|
|
11020
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
11021
|
+
continue;
|
|
11022
|
+
}
|
|
11023
|
+
const line = raw.trim();
|
|
11024
|
+
if (!line) continue;
|
|
11025
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
11026
|
+
if (h) {
|
|
11027
|
+
if (!title) title = stripInline(h[1]);
|
|
11028
|
+
else body.push(stripInline(h[1]));
|
|
11029
|
+
continue;
|
|
11030
|
+
}
|
|
11031
|
+
const img = matchImageLine(line);
|
|
11032
|
+
if (img) {
|
|
11033
|
+
images.push(img);
|
|
11034
|
+
continue;
|
|
11035
|
+
}
|
|
11036
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
11037
|
+
if (li) {
|
|
11038
|
+
const bulletImg = matchImageLine(li[2]);
|
|
11039
|
+
if (bulletImg) {
|
|
11040
|
+
images.push(bulletImg);
|
|
11041
|
+
continue;
|
|
11042
|
+
}
|
|
11043
|
+
body.push(stripInline(li[2]));
|
|
11044
|
+
continue;
|
|
11045
|
+
}
|
|
11046
|
+
if (/^>\s?/.test(line)) {
|
|
11047
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
11048
|
+
continue;
|
|
11049
|
+
}
|
|
11050
|
+
body.push(stripInline(line));
|
|
11051
|
+
}
|
|
11052
|
+
return { title, body, images, charts };
|
|
11053
|
+
}
|
|
11054
|
+
function extractCharts(source) {
|
|
11055
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
11056
|
+
const charts = [];
|
|
11057
|
+
const rest = [];
|
|
11058
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11059
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
11060
|
+
if (!fence) {
|
|
11061
|
+
rest.push(lines[i]);
|
|
11062
|
+
continue;
|
|
11063
|
+
}
|
|
11064
|
+
const kind = chartKind(fence[1]);
|
|
11065
|
+
const body = [];
|
|
11066
|
+
i++;
|
|
11067
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
11068
|
+
body.push(lines[i] ?? "");
|
|
11069
|
+
i++;
|
|
11070
|
+
}
|
|
11071
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
11072
|
+
if (spec) charts.push(spec);
|
|
11073
|
+
else rest.push(...body);
|
|
11074
|
+
}
|
|
11075
|
+
return { charts, rest: rest.join("\n") };
|
|
11076
|
+
}
|
|
11077
|
+
|
|
11078
|
+
// src/core/documents/render.ts
|
|
11079
|
+
var DOCUMENT_MIME = {
|
|
11080
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
11081
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
11082
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
11083
|
+
};
|
|
11084
|
+
function isDocumentFormat(ext) {
|
|
11085
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
11086
|
+
}
|
|
11087
|
+
async function loadImage(ref, load) {
|
|
11088
|
+
if (!load) return null;
|
|
11089
|
+
try {
|
|
11090
|
+
const bytes = await load(ref.url);
|
|
11091
|
+
if (!bytes || !bytes.length) return null;
|
|
11092
|
+
const info = sniffImage(bytes);
|
|
11093
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
11094
|
+
return { ...info, bytes, alt: ref.alt };
|
|
11095
|
+
} catch {
|
|
11096
|
+
return null;
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
11099
|
+
async function renderXlsx(source, opts = {}) {
|
|
11100
|
+
const XLSX = await import('xlsx');
|
|
11101
|
+
const { charts, rest } = extractCharts(source);
|
|
11102
|
+
const name = opts.name ?? "";
|
|
11103
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
11104
|
+
const wb = XLSX.utils.book_new();
|
|
11105
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
11106
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
11107
|
+
charts.forEach((spec, i) => {
|
|
11108
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
11109
|
+
let sheetName = base;
|
|
11110
|
+
let n = 2;
|
|
11111
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
11112
|
+
used.add(sheetName);
|
|
11113
|
+
const aoa = [
|
|
11114
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
11115
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
11116
|
+
];
|
|
11117
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
11118
|
+
});
|
|
11119
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
11120
|
+
}
|
|
11121
|
+
var DOCX_MAX_W = 600;
|
|
11122
|
+
var DOCX_MAX_H = 864;
|
|
11123
|
+
async function renderDocx(md, opts = {}) {
|
|
11124
|
+
const d = await import('docx');
|
|
11125
|
+
const {
|
|
11126
|
+
Document,
|
|
11127
|
+
Packer,
|
|
11128
|
+
Paragraph,
|
|
11129
|
+
TextRun,
|
|
11130
|
+
ImageRun,
|
|
11131
|
+
HeadingLevel,
|
|
11132
|
+
Table,
|
|
11133
|
+
TableRow,
|
|
11134
|
+
TableCell,
|
|
11135
|
+
WidthType,
|
|
11136
|
+
BorderStyle,
|
|
11137
|
+
AlignmentType
|
|
11138
|
+
} = d;
|
|
11139
|
+
const HEADINGS = [
|
|
11140
|
+
HeadingLevel.HEADING_1,
|
|
11141
|
+
HeadingLevel.HEADING_2,
|
|
11142
|
+
HeadingLevel.HEADING_3,
|
|
11143
|
+
HeadingLevel.HEADING_4,
|
|
11144
|
+
HeadingLevel.HEADING_5,
|
|
11145
|
+
HeadingLevel.HEADING_6
|
|
11146
|
+
];
|
|
11147
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
11148
|
+
text: r.text,
|
|
11149
|
+
...r.bold ? { bold: true } : {},
|
|
11150
|
+
...r.italic ? { italics: true } : {},
|
|
11151
|
+
...r.code ? { font: "Courier New" } : {}
|
|
11152
|
+
}));
|
|
11153
|
+
const tableOf = (rows) => new Table({
|
|
11154
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
11155
|
+
rows: rows.map((r) => new TableRow({
|
|
11156
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
11157
|
+
}))
|
|
11158
|
+
});
|
|
11159
|
+
const imageParagraph = (img) => {
|
|
11160
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
11161
|
+
return new Paragraph({
|
|
11162
|
+
children: [new ImageRun({
|
|
11163
|
+
type: img.type,
|
|
11164
|
+
data: img.bytes,
|
|
11165
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
11166
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
11167
|
+
})]
|
|
11168
|
+
});
|
|
11169
|
+
};
|
|
11170
|
+
const asImage = async (text) => {
|
|
11171
|
+
const ref = matchImageLine(text);
|
|
11172
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
11173
|
+
};
|
|
11174
|
+
const children = [];
|
|
11175
|
+
for (const b of parseBlocks(md)) {
|
|
11176
|
+
switch (b.t) {
|
|
11177
|
+
case "heading":
|
|
11178
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
11179
|
+
break;
|
|
11180
|
+
case "para": {
|
|
11181
|
+
const img = await asImage(b.text);
|
|
11182
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
11183
|
+
break;
|
|
11184
|
+
}
|
|
11185
|
+
case "bullet": {
|
|
11186
|
+
const img = await asImage(b.text);
|
|
11187
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
11188
|
+
children: runs(b.text),
|
|
11189
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
11190
|
+
}));
|
|
11191
|
+
break;
|
|
11192
|
+
}
|
|
11193
|
+
case "code":
|
|
11194
|
+
for (const cl of b.lines) {
|
|
11195
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
11196
|
+
}
|
|
11197
|
+
break;
|
|
11198
|
+
case "quote":
|
|
11199
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
11200
|
+
break;
|
|
11201
|
+
case "table":
|
|
11202
|
+
children.push(tableOf(b.rows));
|
|
11203
|
+
break;
|
|
11204
|
+
case "chart":
|
|
11205
|
+
if (b.spec.title) {
|
|
11206
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
11207
|
+
}
|
|
11208
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
11209
|
+
break;
|
|
11210
|
+
case "hr":
|
|
11211
|
+
children.push(new Paragraph({
|
|
11212
|
+
children: [],
|
|
11213
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
11214
|
+
}));
|
|
11215
|
+
break;
|
|
11216
|
+
}
|
|
11217
|
+
}
|
|
11218
|
+
const doc = new Document({
|
|
11219
|
+
numbering: {
|
|
11220
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
11221
|
+
},
|
|
11222
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
11223
|
+
});
|
|
11224
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
11225
|
+
}
|
|
11226
|
+
function pptxConstructor(mod) {
|
|
11227
|
+
let c = mod;
|
|
11228
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
11229
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
11230
|
+
return c;
|
|
11231
|
+
}
|
|
11232
|
+
var SLIDE_H = 7.5;
|
|
11233
|
+
var CONTENT_X = 0.6;
|
|
11234
|
+
var CONTENT_W = 12.1;
|
|
11235
|
+
var CONTENT_BOTTOM = 0.4;
|
|
11236
|
+
async function renderPptx(md, opts = {}) {
|
|
11237
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
11238
|
+
const pptx = new PptxGen();
|
|
11239
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
11240
|
+
const CHART_TYPES = {
|
|
11241
|
+
bar: pptx.ChartType["bar"],
|
|
11242
|
+
line: pptx.ChartType["line"],
|
|
11243
|
+
pie: pptx.ChartType["pie"],
|
|
11244
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
11245
|
+
area: pptx.ChartType["area"],
|
|
11246
|
+
scatter: pptx.ChartType["scatter"]
|
|
11247
|
+
};
|
|
11248
|
+
const slides = splitSlides(md);
|
|
11249
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
11250
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
11251
|
+
const slide = pptx.addSlide();
|
|
11252
|
+
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" });
|
|
11253
|
+
const top = s.title ? 1.5 : 0.5;
|
|
11254
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
11255
|
+
const visuals = [
|
|
11256
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
11257
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
11258
|
+
];
|
|
11259
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
11260
|
+
if (s.body.length) {
|
|
11261
|
+
slide.addText(
|
|
11262
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
11263
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
11264
|
+
);
|
|
11265
|
+
}
|
|
11266
|
+
if (visuals.length) {
|
|
11267
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
11268
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
11269
|
+
const gap = 0.3;
|
|
11270
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
11271
|
+
visuals.forEach((v, i) => {
|
|
11272
|
+
const x = CONTENT_X + i * (w + gap);
|
|
11273
|
+
if (v.kind === "image") {
|
|
11274
|
+
slide.addImage({
|
|
11275
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
11276
|
+
x,
|
|
11277
|
+
y,
|
|
11278
|
+
w,
|
|
11279
|
+
h,
|
|
11280
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
11281
|
+
// render looks worse than a smaller correct one.
|
|
11282
|
+
sizing: { type: "contain", w, h },
|
|
11283
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
11284
|
+
});
|
|
11285
|
+
} else {
|
|
11286
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
11287
|
+
x,
|
|
11288
|
+
y,
|
|
11289
|
+
w,
|
|
11290
|
+
h,
|
|
11291
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
11292
|
+
legendPos: "b",
|
|
11293
|
+
showTitle: !!v.spec.title,
|
|
11294
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
11295
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
11296
|
+
});
|
|
11297
|
+
}
|
|
11298
|
+
});
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
11302
|
+
}
|
|
11303
|
+
function chartData(spec) {
|
|
11304
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
11305
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
11306
|
+
}
|
|
11307
|
+
async function renderDocument(format, source, opts = {}) {
|
|
11308
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
11309
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
11310
|
+
return renderXlsx(source, opts);
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11313
|
+
// src/tools/generate-document.ts
|
|
11314
|
+
var SOURCE_HINT = {
|
|
11315
|
+
docx: "Markdown",
|
|
11316
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
11317
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
11318
|
+
};
|
|
11319
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
11320
|
+
/**
|
|
11321
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
11322
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
11323
|
+
*/
|
|
11324
|
+
constructor(readFile = async (p) => new Uint8Array(await fs5.readFile(p))) {
|
|
11325
|
+
super();
|
|
11326
|
+
this.readFile = readFile;
|
|
11327
|
+
}
|
|
11328
|
+
readFile;
|
|
11329
|
+
name = "generate_document";
|
|
11330
|
+
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.";
|
|
11331
|
+
inputSchema = {
|
|
11332
|
+
type: "object",
|
|
11333
|
+
properties: {
|
|
11334
|
+
path: {
|
|
11335
|
+
type: "string",
|
|
11336
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
11337
|
+
},
|
|
11338
|
+
content: {
|
|
11339
|
+
type: "string",
|
|
11340
|
+
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.'
|
|
11341
|
+
},
|
|
11342
|
+
format: {
|
|
11343
|
+
type: "string",
|
|
11344
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
11345
|
+
}
|
|
11346
|
+
},
|
|
11347
|
+
required: ["path", "content"]
|
|
11348
|
+
};
|
|
11349
|
+
isDangerous() {
|
|
11350
|
+
return true;
|
|
11351
|
+
}
|
|
11352
|
+
async execute(input, options) {
|
|
11353
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11354
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
11355
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
11356
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
11357
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
11358
|
+
const ext = requested || fileExt(filePath);
|
|
11359
|
+
if (!isDocumentFormat(ext)) {
|
|
11360
|
+
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.`;
|
|
11361
|
+
}
|
|
11362
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
11363
|
+
const embedded = [];
|
|
11364
|
+
const skipped = [];
|
|
11365
|
+
const loadImageBytes = async (url) => {
|
|
11366
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
11367
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
11368
|
+
return null;
|
|
11369
|
+
}
|
|
11370
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
11371
|
+
try {
|
|
11372
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
11373
|
+
embedded.push(local);
|
|
11374
|
+
return bytes2;
|
|
11375
|
+
} catch {
|
|
11376
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
11377
|
+
return null;
|
|
11378
|
+
}
|
|
11379
|
+
};
|
|
11380
|
+
if (options.saveSnapshot) {
|
|
11381
|
+
try {
|
|
11382
|
+
const old = await fs5.readFile(absPath, "utf-8");
|
|
11383
|
+
await options.saveSnapshot(absPath, old);
|
|
11384
|
+
} catch {
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11387
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11388
|
+
await fs5.mkdir(path26.dirname(absPath), { recursive: true });
|
|
11389
|
+
await fs5.writeFile(absPath, bytes);
|
|
11390
|
+
const charts = countCharts(content);
|
|
11391
|
+
return [
|
|
11392
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
11393
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
11394
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
11395
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
11396
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
11397
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
11398
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
11399
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
11400
|
+
].filter(Boolean).join("\n");
|
|
11401
|
+
}
|
|
11402
|
+
};
|
|
11403
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
11404
|
+
function countCharts(source) {
|
|
11405
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
11406
|
+
}
|
|
11407
|
+
function buildDocumentTools(readFile) {
|
|
11408
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
11409
|
+
}
|
|
10307
11410
|
|
|
10308
11411
|
// src/core/run-breaker.ts
|
|
10309
11412
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -10626,35 +11729,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
10626
11729
|
}
|
|
10627
11730
|
}
|
|
10628
11731
|
};
|
|
10629
|
-
var WorkspaceSandboxError = class extends Error {
|
|
10630
|
-
constructor(attempted, workspaceRoot) {
|
|
10631
|
-
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10632
|
-
this.name = "WorkspaceSandboxError";
|
|
10633
|
-
}
|
|
10634
|
-
};
|
|
10635
|
-
function resolveInWorkspace(workspaceRoot, input) {
|
|
10636
|
-
if (typeof input !== "string" || input.length === 0) {
|
|
10637
|
-
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10638
|
-
}
|
|
10639
|
-
const root = path24.resolve(workspaceRoot);
|
|
10640
|
-
const abs = path24.isAbsolute(input) ? path24.resolve(input) : path24.resolve(root, input);
|
|
10641
|
-
const rel = path24.relative(root, abs);
|
|
10642
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24.isAbsolute(rel)) {
|
|
10643
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10644
|
-
}
|
|
10645
|
-
try {
|
|
10646
|
-
const real = fs23.realpathSync(abs);
|
|
10647
|
-
const realRel = path24.relative(root, real);
|
|
10648
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24.isAbsolute(realRel))) {
|
|
10649
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10650
|
-
}
|
|
10651
|
-
} catch (e) {
|
|
10652
|
-
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10653
|
-
}
|
|
10654
|
-
return abs;
|
|
10655
|
-
}
|
|
10656
|
-
|
|
10657
|
-
// src/tools/file.ts
|
|
10658
11732
|
var FileReadTool = class extends BaseTool {
|
|
10659
11733
|
name = "file_read";
|
|
10660
11734
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -10672,7 +11746,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
10672
11746
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10673
11747
|
const offset = input["offset"] ?? 1;
|
|
10674
11748
|
const limit = input["limit"];
|
|
10675
|
-
const content = await
|
|
11749
|
+
const content = await fs5.readFile(absPath, "utf-8");
|
|
10676
11750
|
const lines = content.split("\n");
|
|
10677
11751
|
const start = Math.max(0, offset - 1);
|
|
10678
11752
|
const end = limit ? start + limit : lines.length;
|
|
@@ -10701,13 +11775,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
10701
11775
|
const content = input["content"];
|
|
10702
11776
|
if (options.saveSnapshot) {
|
|
10703
11777
|
try {
|
|
10704
|
-
const oldContent = await
|
|
11778
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10705
11779
|
await options.saveSnapshot(absPath, oldContent);
|
|
10706
11780
|
} catch {
|
|
10707
11781
|
}
|
|
10708
11782
|
}
|
|
10709
|
-
await
|
|
10710
|
-
await
|
|
11783
|
+
await fs5.mkdir(path26.dirname(absPath), { recursive: true });
|
|
11784
|
+
await fs5.writeFile(absPath, content, "utf-8");
|
|
10711
11785
|
return `Written ${content.length} characters to ${filePath}`;
|
|
10712
11786
|
}
|
|
10713
11787
|
};
|
|
@@ -10733,7 +11807,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10733
11807
|
const oldString = input["old_string"];
|
|
10734
11808
|
const newString = input["new_string"];
|
|
10735
11809
|
const replaceAll = input["replace_all"] ?? false;
|
|
10736
|
-
const rawContent = await
|
|
11810
|
+
const rawContent = await fs5.readFile(absPath, "utf-8");
|
|
10737
11811
|
if (options.saveSnapshot) {
|
|
10738
11812
|
await options.saveSnapshot(absPath, rawContent);
|
|
10739
11813
|
}
|
|
@@ -10745,7 +11819,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10745
11819
|
);
|
|
10746
11820
|
}
|
|
10747
11821
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
10748
|
-
await
|
|
11822
|
+
await fs5.writeFile(absPath, updated, "utf-8");
|
|
10749
11823
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
10750
11824
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
10751
11825
|
}
|
|
@@ -10768,12 +11842,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
10768
11842
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10769
11843
|
if (options.saveSnapshot) {
|
|
10770
11844
|
try {
|
|
10771
|
-
const oldContent = await
|
|
11845
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10772
11846
|
await options.saveSnapshot(absPath, oldContent);
|
|
10773
11847
|
} catch {
|
|
10774
11848
|
}
|
|
10775
11849
|
}
|
|
10776
|
-
await
|
|
11850
|
+
await fs5.rm(absPath, { recursive: false });
|
|
10777
11851
|
return `Deleted ${filePath}`;
|
|
10778
11852
|
}
|
|
10779
11853
|
};
|
|
@@ -10790,7 +11864,7 @@ var FileListTool = class extends BaseTool {
|
|
|
10790
11864
|
async execute(input, _options) {
|
|
10791
11865
|
const inputPath = input["path"] || ".";
|
|
10792
11866
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
10793
|
-
const entries = await
|
|
11867
|
+
const entries = await fs5.readdir(absPath, { withFileTypes: true });
|
|
10794
11868
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
10795
11869
|
}
|
|
10796
11870
|
};
|
|
@@ -11203,8 +12277,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11203
12277
|
}
|
|
11204
12278
|
};
|
|
11205
12279
|
async function fileToImageAttachment(filePath) {
|
|
11206
|
-
const data = await
|
|
11207
|
-
const ext =
|
|
12280
|
+
const data = await fs5.readFile(filePath);
|
|
12281
|
+
const ext = path26.extname(filePath).toLowerCase();
|
|
11208
12282
|
const mimeMap = {
|
|
11209
12283
|
".jpg": "image/jpeg",
|
|
11210
12284
|
".jpeg": "image/jpeg",
|
|
@@ -11238,14 +12312,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11238
12312
|
const filePath = input["path"];
|
|
11239
12313
|
const content = input["content"];
|
|
11240
12314
|
const title = input["title"];
|
|
11241
|
-
const dir =
|
|
11242
|
-
if (!
|
|
11243
|
-
|
|
12315
|
+
const dir = path26.dirname(filePath);
|
|
12316
|
+
if (!fs25.existsSync(dir)) {
|
|
12317
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
11244
12318
|
}
|
|
11245
12319
|
return new Promise((resolve2, reject) => {
|
|
11246
12320
|
try {
|
|
11247
12321
|
const doc = new PDFDocument({ margin: 50 });
|
|
11248
|
-
const stream =
|
|
12322
|
+
const stream = fs25.createWriteStream(filePath);
|
|
11249
12323
|
doc.pipe(stream);
|
|
11250
12324
|
if (title) {
|
|
11251
12325
|
doc.info["Title"] = title;
|
|
@@ -11323,22 +12397,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11323
12397
|
}
|
|
11324
12398
|
cmdPrefix = NODE_CMD;
|
|
11325
12399
|
}
|
|
11326
|
-
const tmpDir =
|
|
11327
|
-
if (!
|
|
11328
|
-
|
|
12400
|
+
const tmpDir = path26.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12401
|
+
if (!fs25.existsSync(tmpDir)) {
|
|
12402
|
+
fs25.mkdirSync(tmpDir, { recursive: true });
|
|
11329
12403
|
}
|
|
11330
12404
|
const extension = language === "python" ? "py" : "js";
|
|
11331
12405
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
11332
|
-
const filePath =
|
|
11333
|
-
|
|
12406
|
+
const filePath = path26.join(tmpDir, fileName);
|
|
12407
|
+
fs25.writeFileSync(filePath, code, "utf-8");
|
|
11334
12408
|
const execArgs = [filePath, ...args];
|
|
11335
12409
|
return new Promise((resolve2) => {
|
|
11336
12410
|
const startMs = Date.now();
|
|
11337
12411
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11338
12412
|
const duration = Date.now() - startMs;
|
|
11339
12413
|
try {
|
|
11340
|
-
if (
|
|
11341
|
-
|
|
12414
|
+
if (fs25.existsSync(filePath)) {
|
|
12415
|
+
fs25.unlinkSync(filePath);
|
|
11342
12416
|
}
|
|
11343
12417
|
} catch (cleanupErr) {
|
|
11344
12418
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -11650,7 +12724,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11650
12724
|
};
|
|
11651
12725
|
async execute(input, _options) {
|
|
11652
12726
|
const pattern = input["pattern"];
|
|
11653
|
-
const searchPath = input["path"] ?
|
|
12727
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11654
12728
|
const matches = await glob(pattern, {
|
|
11655
12729
|
cwd: searchPath,
|
|
11656
12730
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -11663,7 +12737,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11663
12737
|
const withMtime = await Promise.all(
|
|
11664
12738
|
matches.map(async (rel) => {
|
|
11665
12739
|
try {
|
|
11666
|
-
const stat = await
|
|
12740
|
+
const stat = await fs5.stat(path26.join(searchPath, rel));
|
|
11667
12741
|
return { rel, mtime: stat.mtimeMs };
|
|
11668
12742
|
} catch {
|
|
11669
12743
|
return { rel, mtime: 0 };
|
|
@@ -11712,7 +12786,7 @@ var GrepTool = class extends BaseTool {
|
|
|
11712
12786
|
};
|
|
11713
12787
|
async execute(input, _options) {
|
|
11714
12788
|
const pattern = input["pattern"];
|
|
11715
|
-
const searchPath = input["path"] ?
|
|
12789
|
+
const searchPath = input["path"] ? path26.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11716
12790
|
const globPattern = input["glob"];
|
|
11717
12791
|
const outputMode = input["output_mode"] ?? "content";
|
|
11718
12792
|
const context = input["context"] ?? 0;
|
|
@@ -11766,15 +12840,15 @@ var GrepTool = class extends BaseTool {
|
|
|
11766
12840
|
nodir: true
|
|
11767
12841
|
});
|
|
11768
12842
|
} catch {
|
|
11769
|
-
files = [
|
|
12843
|
+
files = [path26.relative(searchPath, searchPath) || "."];
|
|
11770
12844
|
}
|
|
11771
12845
|
const results = [];
|
|
11772
12846
|
let totalCount = 0;
|
|
11773
12847
|
for (const rel of files) {
|
|
11774
|
-
const abs =
|
|
12848
|
+
const abs = path26.join(searchPath, rel);
|
|
11775
12849
|
let content;
|
|
11776
12850
|
try {
|
|
11777
|
-
content = await
|
|
12851
|
+
content = await fs5.readFile(abs, "utf-8");
|
|
11778
12852
|
} catch {
|
|
11779
12853
|
continue;
|
|
11780
12854
|
}
|
|
@@ -12158,10 +13232,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
12158
13232
|
}
|
|
12159
13233
|
isIgnored(filePath) {
|
|
12160
13234
|
if (!filePath) return false;
|
|
12161
|
-
const abs =
|
|
12162
|
-
const rel =
|
|
12163
|
-
if (!rel || rel.startsWith("..") ||
|
|
12164
|
-
const posixRel = rel.split(
|
|
13235
|
+
const abs = path26.resolve(this.workspaceRoot, filePath);
|
|
13236
|
+
const rel = path26.relative(this.workspaceRoot, abs);
|
|
13237
|
+
if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) return true;
|
|
13238
|
+
const posixRel = rel.split(path26.sep).join("/");
|
|
12165
13239
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12166
13240
|
}
|
|
12167
13241
|
};
|
|
@@ -12395,22 +13469,22 @@ var FileMcpOAuthStore = class {
|
|
|
12395
13469
|
filePath;
|
|
12396
13470
|
load() {
|
|
12397
13471
|
try {
|
|
12398
|
-
return JSON.parse(
|
|
13472
|
+
return JSON.parse(fs25.readFileSync(this.filePath, "utf-8"));
|
|
12399
13473
|
} catch {
|
|
12400
13474
|
return void 0;
|
|
12401
13475
|
}
|
|
12402
13476
|
}
|
|
12403
13477
|
save(state) {
|
|
12404
|
-
|
|
12405
|
-
|
|
13478
|
+
fs25.mkdirSync(path26.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13479
|
+
fs25.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
12406
13480
|
try {
|
|
12407
|
-
|
|
13481
|
+
fs25.chmodSync(this.filePath, 384);
|
|
12408
13482
|
} catch {
|
|
12409
13483
|
}
|
|
12410
13484
|
}
|
|
12411
13485
|
clear() {
|
|
12412
13486
|
try {
|
|
12413
|
-
|
|
13487
|
+
fs25.rmSync(this.filePath);
|
|
12414
13488
|
} catch {
|
|
12415
13489
|
}
|
|
12416
13490
|
}
|
|
@@ -13307,6 +14381,14 @@ function heuristicAnalyze(prompt) {
|
|
|
13307
14381
|
const estimatedTokens = wordCount * 5;
|
|
13308
14382
|
return { type, complexity, requiresReasoning, requiresVision, estimatedTokens, confidence };
|
|
13309
14383
|
}
|
|
14384
|
+
function hashPrompt(prompt) {
|
|
14385
|
+
let hash = 2166136261;
|
|
14386
|
+
for (let index = 0; index < prompt.length; index++) {
|
|
14387
|
+
hash ^= prompt.charCodeAt(index);
|
|
14388
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
14389
|
+
}
|
|
14390
|
+
return `${prompt.length}:${hash.toString(36)}`;
|
|
14391
|
+
}
|
|
13310
14392
|
var analysisCache = /* @__PURE__ */ new Map();
|
|
13311
14393
|
var TASK_TYPE_TAGS = {
|
|
13312
14394
|
code: ["code", "instruction"],
|
|
@@ -13350,7 +14432,7 @@ var TaskAnalyzer = class {
|
|
|
13350
14432
|
* Low confidence prompts fall back to a conservative mixed/moderate profile.
|
|
13351
14433
|
*/
|
|
13352
14434
|
async analyze(prompt) {
|
|
13353
|
-
const cacheKey = prompt
|
|
14435
|
+
const cacheKey = hashPrompt(prompt);
|
|
13354
14436
|
const cached = analysisCache.get(cacheKey);
|
|
13355
14437
|
if (cached) {
|
|
13356
14438
|
this.lastProfile = cached;
|
|
@@ -13449,7 +14531,7 @@ var TaskAnalyzer = class {
|
|
|
13449
14531
|
analysisCache.clear();
|
|
13450
14532
|
}
|
|
13451
14533
|
};
|
|
13452
|
-
var DEFAULT_STATS_FILE =
|
|
14534
|
+
var DEFAULT_STATS_FILE = path26.join(os6.homedir(), ".cascade", "model-perf.json");
|
|
13453
14535
|
var ModelPerformanceTracker = class {
|
|
13454
14536
|
stats = /* @__PURE__ */ new Map();
|
|
13455
14537
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -13469,7 +14551,7 @@ var ModelPerformanceTracker = class {
|
|
|
13469
14551
|
if (this.loaded) return;
|
|
13470
14552
|
this.loaded = true;
|
|
13471
14553
|
try {
|
|
13472
|
-
const raw = await
|
|
14554
|
+
const raw = await fs5.readFile(this.statsFile, "utf-8");
|
|
13473
14555
|
const parsed = JSON.parse(raw);
|
|
13474
14556
|
if (parsed.models) {
|
|
13475
14557
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -13489,15 +14571,15 @@ var ModelPerformanceTracker = class {
|
|
|
13489
14571
|
async save() {
|
|
13490
14572
|
if (this.readOnly) return;
|
|
13491
14573
|
try {
|
|
13492
|
-
await
|
|
14574
|
+
await fs5.mkdir(path26.dirname(this.statsFile), { recursive: true });
|
|
13493
14575
|
const modelsObj = {};
|
|
13494
14576
|
const featuresObj = {};
|
|
13495
14577
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
13496
14578
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
13497
14579
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
13498
14580
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
13499
|
-
await
|
|
13500
|
-
await
|
|
14581
|
+
await fs5.writeFile(tmp, json, "utf-8");
|
|
14582
|
+
await fs5.rename(tmp, this.statsFile);
|
|
13501
14583
|
} catch {
|
|
13502
14584
|
}
|
|
13503
14585
|
}
|
|
@@ -14037,9 +15119,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14037
15119
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
14038
15120
|
async loadPersistedTools() {
|
|
14039
15121
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14040
|
-
const file =
|
|
15122
|
+
const file = path26.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
14041
15123
|
try {
|
|
14042
|
-
const raw = await
|
|
15124
|
+
const raw = await fs5.readFile(file, "utf-8");
|
|
14043
15125
|
const specs = JSON.parse(raw);
|
|
14044
15126
|
if (!Array.isArray(specs)) return;
|
|
14045
15127
|
let loaded = 0;
|
|
@@ -14061,11 +15143,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14061
15143
|
}
|
|
14062
15144
|
async persist() {
|
|
14063
15145
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14064
|
-
const dir =
|
|
14065
|
-
const file =
|
|
15146
|
+
const dir = path26.join(this.workspacePath, ".cascade");
|
|
15147
|
+
const file = path26.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14066
15148
|
try {
|
|
14067
|
-
await
|
|
14068
|
-
await
|
|
15149
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
15150
|
+
await fs5.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
14069
15151
|
} catch (err) {
|
|
14070
15152
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
14071
15153
|
}
|
|
@@ -14082,12 +15164,12 @@ var WorldStateDB = class {
|
|
|
14082
15164
|
constructor(workspacePath, debugMode = false) {
|
|
14083
15165
|
this.workspacePath = workspacePath;
|
|
14084
15166
|
this.debugMode = debugMode;
|
|
14085
|
-
const cascadeDir =
|
|
14086
|
-
if (!
|
|
14087
|
-
|
|
15167
|
+
const cascadeDir = path26.join(workspacePath, ".cascade");
|
|
15168
|
+
if (!fs25.existsSync(cascadeDir)) {
|
|
15169
|
+
fs25.mkdirSync(cascadeDir, { recursive: true });
|
|
14088
15170
|
}
|
|
14089
|
-
this.keyPath =
|
|
14090
|
-
this.dbPath =
|
|
15171
|
+
this.keyPath = path26.join(cascadeDir, "world_state.key");
|
|
15172
|
+
this.dbPath = path26.join(cascadeDir, "world_state.db");
|
|
14091
15173
|
this.initEncryptionKey();
|
|
14092
15174
|
this.db = new Database(this.dbPath);
|
|
14093
15175
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14144,11 +15226,11 @@ var WorldStateDB = class {
|
|
|
14144
15226
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14145
15227
|
}
|
|
14146
15228
|
initEncryptionKey() {
|
|
14147
|
-
if (
|
|
14148
|
-
this.encryptionKey =
|
|
15229
|
+
if (fs25.existsSync(this.keyPath)) {
|
|
15230
|
+
this.encryptionKey = fs25.readFileSync(this.keyPath);
|
|
14149
15231
|
} else {
|
|
14150
15232
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
14151
|
-
|
|
15233
|
+
fs25.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14152
15234
|
}
|
|
14153
15235
|
}
|
|
14154
15236
|
encrypt(text) {
|
|
@@ -14434,9 +15516,9 @@ var WorldStateDB = class {
|
|
|
14434
15516
|
dumpDebugIfNeeded() {
|
|
14435
15517
|
if (!this.debugMode) return;
|
|
14436
15518
|
try {
|
|
14437
|
-
const dumpPath =
|
|
15519
|
+
const dumpPath = path26.join(os6.tmpdir(), "cascade_world_state_debug.json");
|
|
14438
15520
|
const entries = this.getAllEntries();
|
|
14439
|
-
|
|
15521
|
+
fs25.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
14440
15522
|
} catch (err) {
|
|
14441
15523
|
console.error("Failed to dump debug world state", err);
|
|
14442
15524
|
}
|
|
@@ -14445,6 +15527,128 @@ var WorldStateDB = class {
|
|
|
14445
15527
|
this.db.close();
|
|
14446
15528
|
}
|
|
14447
15529
|
};
|
|
15530
|
+
var RESUME_SCHEMA_VERSION = 1;
|
|
15531
|
+
var DEFAULT_MAX_CHECKPOINTS = 5;
|
|
15532
|
+
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15533
|
+
var FILE_PATTERN = /^(\d+)-(.+)\.json$/;
|
|
15534
|
+
function safeId(taskId) {
|
|
15535
|
+
const cleaned = taskId.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
15536
|
+
return cleaned.slice(0, 100) || "run";
|
|
15537
|
+
}
|
|
15538
|
+
function isCheckpoint(value) {
|
|
15539
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15540
|
+
const c = value;
|
|
15541
|
+
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);
|
|
15542
|
+
}
|
|
15543
|
+
var ResumeStore = class {
|
|
15544
|
+
dir;
|
|
15545
|
+
maxCheckpoints;
|
|
15546
|
+
maxAgeMs;
|
|
15547
|
+
now;
|
|
15548
|
+
constructor(options) {
|
|
15549
|
+
this.dir = options.dir;
|
|
15550
|
+
this.maxCheckpoints = options.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
|
|
15551
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
15552
|
+
this.now = options.now ?? Date.now;
|
|
15553
|
+
}
|
|
15554
|
+
/**
|
|
15555
|
+
* Persist a checkpoint and prune. Never throws: failing to save a resume file
|
|
15556
|
+
* must not turn a recoverable stop into a crash, which would be a strictly
|
|
15557
|
+
* worse outcome than the one this store exists to improve.
|
|
15558
|
+
*/
|
|
15559
|
+
async save(checkpoint) {
|
|
15560
|
+
try {
|
|
15561
|
+
const timestamp = this.now();
|
|
15562
|
+
const record = {
|
|
15563
|
+
...checkpoint,
|
|
15564
|
+
version: RESUME_SCHEMA_VERSION,
|
|
15565
|
+
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
15566
|
+
};
|
|
15567
|
+
await promises.mkdir(this.dir, { recursive: true });
|
|
15568
|
+
const finalPath = path26.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
15569
|
+
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
15570
|
+
await promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
15571
|
+
await promises.rename(tempPath, finalPath);
|
|
15572
|
+
await this.prune();
|
|
15573
|
+
} catch {
|
|
15574
|
+
}
|
|
15575
|
+
}
|
|
15576
|
+
/** Valid, unexpired checkpoints, newest first. */
|
|
15577
|
+
async list() {
|
|
15578
|
+
let entries;
|
|
15579
|
+
try {
|
|
15580
|
+
entries = await promises.readdir(this.dir);
|
|
15581
|
+
} catch {
|
|
15582
|
+
return [];
|
|
15583
|
+
}
|
|
15584
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15585
|
+
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);
|
|
15586
|
+
const checkpoints = [];
|
|
15587
|
+
for (const candidate of candidates) {
|
|
15588
|
+
try {
|
|
15589
|
+
const raw = await promises.readFile(path26.join(this.dir, candidate.name), "utf-8");
|
|
15590
|
+
const parsed = JSON.parse(raw);
|
|
15591
|
+
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
15592
|
+
} catch {
|
|
15593
|
+
continue;
|
|
15594
|
+
}
|
|
15595
|
+
}
|
|
15596
|
+
return checkpoints;
|
|
15597
|
+
}
|
|
15598
|
+
/** The most recent resumable run, or null. */
|
|
15599
|
+
async latest() {
|
|
15600
|
+
return (await this.list())[0] ?? null;
|
|
15601
|
+
}
|
|
15602
|
+
/**
|
|
15603
|
+
* Load a checkpoint and delete it in the same step. Resuming twice from one
|
|
15604
|
+
* checkpoint would re-run restored work, so consumption is part of reading.
|
|
15605
|
+
*/
|
|
15606
|
+
async consume(taskId) {
|
|
15607
|
+
const all = await this.list();
|
|
15608
|
+
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
15609
|
+
if (!target) return null;
|
|
15610
|
+
await this.delete(target.taskId);
|
|
15611
|
+
return target;
|
|
15612
|
+
}
|
|
15613
|
+
/** Remove every checkpoint for a task id. */
|
|
15614
|
+
async delete(taskId) {
|
|
15615
|
+
try {
|
|
15616
|
+
const entries = await promises.readdir(this.dir);
|
|
15617
|
+
const wanted = safeId(taskId);
|
|
15618
|
+
await Promise.all(entries.map(async (name) => {
|
|
15619
|
+
const match = FILE_PATTERN.exec(name);
|
|
15620
|
+
if (match?.[2] === wanted) {
|
|
15621
|
+
await promises.rm(path26.join(this.dir, name), { force: true });
|
|
15622
|
+
}
|
|
15623
|
+
}));
|
|
15624
|
+
} catch {
|
|
15625
|
+
}
|
|
15626
|
+
}
|
|
15627
|
+
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
15628
|
+
async prune() {
|
|
15629
|
+
try {
|
|
15630
|
+
const entries = await promises.readdir(this.dir);
|
|
15631
|
+
const cutoff = this.now() - this.maxAgeMs;
|
|
15632
|
+
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);
|
|
15633
|
+
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
15634
|
+
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
15635
|
+
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
15636
|
+
(name) => promises.rm(path26.join(this.dir, name), { force: true })
|
|
15637
|
+
));
|
|
15638
|
+
} catch {
|
|
15639
|
+
}
|
|
15640
|
+
}
|
|
15641
|
+
};
|
|
15642
|
+
function summarizeCompleted(completed, perNodeChars = 500) {
|
|
15643
|
+
if (!completed.length) return "";
|
|
15644
|
+
const lines = completed.map((node) => {
|
|
15645
|
+
const body = node.output.length > perNodeChars ? `${node.output.slice(0, perNodeChars)}\u2026` : node.output;
|
|
15646
|
+
return `- [${node.status}] ${node.title}
|
|
15647
|
+
${body.replace(/\n/g, "\n ")}`;
|
|
15648
|
+
});
|
|
15649
|
+
return `ALREADY COMPLETED in a previous attempt \u2014 do NOT redo these:
|
|
15650
|
+
${lines.join("\n")}`;
|
|
15651
|
+
}
|
|
14448
15652
|
var ignore2 = ignoreFactory.default ?? ignoreFactory;
|
|
14449
15653
|
var PrivacyPaths = class {
|
|
14450
15654
|
localOnly;
|
|
@@ -14491,9 +15695,9 @@ var CascadeIgnore = class {
|
|
|
14491
15695
|
]);
|
|
14492
15696
|
}
|
|
14493
15697
|
async load(workspacePath) {
|
|
14494
|
-
const filePath =
|
|
15698
|
+
const filePath = path26.join(workspacePath, ".cascadeignore");
|
|
14495
15699
|
try {
|
|
14496
|
-
const content = await
|
|
15700
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
14497
15701
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
14498
15702
|
this.ig.add(lines);
|
|
14499
15703
|
this.loaded = true;
|
|
@@ -14502,7 +15706,7 @@ var CascadeIgnore = class {
|
|
|
14502
15706
|
}
|
|
14503
15707
|
isIgnored(filePath, workspacePath) {
|
|
14504
15708
|
try {
|
|
14505
|
-
const relative = workspacePath ?
|
|
15709
|
+
const relative = workspacePath ? path26.relative(workspacePath, filePath) : filePath;
|
|
14506
15710
|
return this.ig.ignores(relative);
|
|
14507
15711
|
} catch {
|
|
14508
15712
|
return false;
|
|
@@ -14845,7 +16049,7 @@ var WorkspaceIndex = class {
|
|
|
14845
16049
|
isIgnored;
|
|
14846
16050
|
chunker;
|
|
14847
16051
|
constructor(opts) {
|
|
14848
|
-
this.root =
|
|
16052
|
+
this.root = path26.resolve(opts.root);
|
|
14849
16053
|
this.db = opts.db;
|
|
14850
16054
|
this.namespace = opts.namespace ?? "code";
|
|
14851
16055
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -14872,19 +16076,19 @@ var WorkspaceIndex = class {
|
|
|
14872
16076
|
const walk = async (dir) => {
|
|
14873
16077
|
let entries;
|
|
14874
16078
|
try {
|
|
14875
|
-
entries = await
|
|
16079
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
14876
16080
|
} catch {
|
|
14877
16081
|
return;
|
|
14878
16082
|
}
|
|
14879
16083
|
for (const entry of entries) {
|
|
14880
|
-
const abs =
|
|
16084
|
+
const abs = path26.join(dir, entry.name);
|
|
14881
16085
|
if (entry.isDirectory()) {
|
|
14882
16086
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
14883
16087
|
await walk(abs);
|
|
14884
16088
|
} else if (entry.isFile()) {
|
|
14885
|
-
const ext =
|
|
16089
|
+
const ext = path26.extname(entry.name).slice(1).toLowerCase();
|
|
14886
16090
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
14887
|
-
out.push(
|
|
16091
|
+
out.push(path26.relative(this.root, abs));
|
|
14888
16092
|
}
|
|
14889
16093
|
}
|
|
14890
16094
|
};
|
|
@@ -14914,10 +16118,10 @@ var WorkspaceIndex = class {
|
|
|
14914
16118
|
const entries = [];
|
|
14915
16119
|
for (const rel of rels) {
|
|
14916
16120
|
try {
|
|
14917
|
-
const abs =
|
|
14918
|
-
const stat = await
|
|
16121
|
+
const abs = path26.join(this.root, rel);
|
|
16122
|
+
const stat = await fs5.stat(abs);
|
|
14919
16123
|
if (stat.size > this.maxFileBytes) continue;
|
|
14920
|
-
const buf = await
|
|
16124
|
+
const buf = await fs5.readFile(abs);
|
|
14921
16125
|
if (buf.includes(0)) continue;
|
|
14922
16126
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
14923
16127
|
} catch {
|
|
@@ -15452,8 +16656,16 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15452
16656
|
/** Orchestration decisions for the CURRENT run — cleared on each run(). */
|
|
15453
16657
|
decisionLog = [];
|
|
15454
16658
|
initialized = false;
|
|
15455
|
-
/**
|
|
16659
|
+
/**
|
|
16660
|
+
* Last interrupted task — powers /continue (resumeRun). Kept in memory as a
|
|
16661
|
+
* fast path for the same-process case; the durable copy lives in `resumeStore`
|
|
16662
|
+
* so a crash or an exit no longer throws away everything already finished.
|
|
16663
|
+
*/
|
|
15456
16664
|
lastInterruptedRun;
|
|
16665
|
+
/** Durable resume checkpoints. See core/orchestration/resume-store.ts. */
|
|
16666
|
+
resumeStore;
|
|
16667
|
+
/** Section results of the CURRENT run, so an interruption can checkpoint them. */
|
|
16668
|
+
currentRunCompleted = [];
|
|
15457
16669
|
initPromise;
|
|
15458
16670
|
store;
|
|
15459
16671
|
audit;
|
|
@@ -15486,14 +16698,21 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15486
16698
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
15487
16699
|
try {
|
|
15488
16700
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
15489
|
-
fileDeadModelPersistence(
|
|
16701
|
+
fileDeadModelPersistence(path26.join(workspacePath, ".cascade", "dead-models.json"))
|
|
15490
16702
|
));
|
|
15491
16703
|
} catch {
|
|
15492
16704
|
}
|
|
16705
|
+
try {
|
|
16706
|
+
this.resumeStore = new ResumeStore({
|
|
16707
|
+
dir: path26.join(workspacePath, ".cascade", "resume")
|
|
16708
|
+
});
|
|
16709
|
+
} catch {
|
|
16710
|
+
}
|
|
15493
16711
|
this.multimodal = new MultimodalRegistry(
|
|
15494
16712
|
(this.config.providers ?? []).map((p) => p.type)
|
|
15495
16713
|
);
|
|
15496
16714
|
this.registerMediaTools(workspacePath);
|
|
16715
|
+
this.registerDocumentTools(workspacePath);
|
|
15497
16716
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15498
16717
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15499
16718
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15573,8 +16792,8 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15573
16792
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
15574
16793
|
return;
|
|
15575
16794
|
}
|
|
15576
|
-
const dbPath = ci.dbPath ||
|
|
15577
|
-
|
|
16795
|
+
const dbPath = ci.dbPath || path26.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16796
|
+
fs25.mkdirSync(path26.dirname(dbPath), { recursive: true });
|
|
15578
16797
|
const db = new Database(dbPath);
|
|
15579
16798
|
db.pragma("journal_mode = WAL");
|
|
15580
16799
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -15914,6 +17133,35 @@ ${input}` }],
|
|
|
15914
17133
|
this.toolRegistry.register(tool);
|
|
15915
17134
|
}
|
|
15916
17135
|
}
|
|
17136
|
+
/**
|
|
17137
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
17138
|
+
*
|
|
17139
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
17140
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
17141
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
17142
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
17143
|
+
* file_write).
|
|
17144
|
+
*
|
|
17145
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
17146
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
17147
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
17148
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
17149
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
17150
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
17151
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
17152
|
+
*/
|
|
17153
|
+
registerDocumentTools(workspacePath) {
|
|
17154
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
17155
|
+
const tools = buildDocumentTools(async (p) => {
|
|
17156
|
+
const fsp = await import('fs/promises');
|
|
17157
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
17158
|
+
});
|
|
17159
|
+
for (const tool of tools) {
|
|
17160
|
+
if (denied.has(tool.name)) continue;
|
|
17161
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
17162
|
+
this.toolRegistry.register(tool);
|
|
17163
|
+
}
|
|
17164
|
+
}
|
|
15917
17165
|
/**
|
|
15918
17166
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
15919
17167
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -15925,10 +17173,43 @@ ${input}` }],
|
|
|
15925
17173
|
setFeedbackSource(source) {
|
|
15926
17174
|
this.router.setFeedbackSource(source);
|
|
15927
17175
|
}
|
|
15928
|
-
/**
|
|
17176
|
+
/**
|
|
17177
|
+
* Write a durable checkpoint for an interrupted run.
|
|
17178
|
+
*
|
|
17179
|
+
* Best-effort throughout: a run is already ending badly on every path that
|
|
17180
|
+
* calls this, and failing to record a resume file must not make that ending
|
|
17181
|
+
* worse. Nothing here is allowed to throw.
|
|
17182
|
+
*/
|
|
17183
|
+
async checkpointRun(taskId, prompt, partialOutput, reason, detail) {
|
|
17184
|
+
if (!this.currentRunCompleted.length && !partialOutput) return;
|
|
17185
|
+
try {
|
|
17186
|
+
await this.resumeStore?.save({
|
|
17187
|
+
taskId,
|
|
17188
|
+
prompt,
|
|
17189
|
+
reason,
|
|
17190
|
+
...detail ? { detail } : {},
|
|
17191
|
+
partialOutput,
|
|
17192
|
+
completed: [...this.currentRunCompleted]
|
|
17193
|
+
});
|
|
17194
|
+
} catch {
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
/** True when a task can be resumed via /continue (this process or a previous one). */
|
|
15929
17198
|
hasResumableRun() {
|
|
15930
17199
|
return this.lastInterruptedRun != null;
|
|
15931
17200
|
}
|
|
17201
|
+
/**
|
|
17202
|
+
* True when a checkpoint from ANY process is resumable. Async because the
|
|
17203
|
+
* durable check touches disk; `hasResumableRun` remains the sync in-memory
|
|
17204
|
+
* answer for callers already on a hot path.
|
|
17205
|
+
*/
|
|
17206
|
+
async hasDurableResume() {
|
|
17207
|
+
return await this.resumeStore?.latest() != null;
|
|
17208
|
+
}
|
|
17209
|
+
/** Interrupted runs available to resume, newest first. */
|
|
17210
|
+
async listResumableRuns() {
|
|
17211
|
+
return await this.resumeStore?.list() ?? [];
|
|
17212
|
+
}
|
|
15932
17213
|
/**
|
|
15933
17214
|
* Raise the per-run token budget for a resume and return the continuation
|
|
15934
17215
|
* prompt (or null when nothing is resumable). Consumes the interrupted-run
|
|
@@ -15939,22 +17220,54 @@ ${input}` }],
|
|
|
15939
17220
|
const last = this.lastInterruptedRun;
|
|
15940
17221
|
if (!last) return null;
|
|
15941
17222
|
this.lastInterruptedRun = void 0;
|
|
15942
|
-
|
|
17223
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17224
|
+
return this.buildResumePrompt(last.prompt, last.partialOutput, []);
|
|
17225
|
+
}
|
|
17226
|
+
/**
|
|
17227
|
+
* Durable counterpart to prepareResume: recovers a run interrupted in ANY
|
|
17228
|
+
* process, including one that crashed before it could return. Consumes the
|
|
17229
|
+
* checkpoint, so the same finished work is never restored into two runs.
|
|
17230
|
+
*/
|
|
17231
|
+
async prepareDurableResume(opts = {}) {
|
|
17232
|
+
const checkpoint = await this.resumeStore?.consume(opts.taskId);
|
|
17233
|
+
if (!checkpoint) return null;
|
|
17234
|
+
this.lastInterruptedRun = void 0;
|
|
17235
|
+
this.raiseBudgetForResume(opts.maxTokens);
|
|
17236
|
+
return this.buildResumePrompt(checkpoint.prompt, checkpoint.partialOutput, checkpoint.completed);
|
|
17237
|
+
}
|
|
17238
|
+
/** A resume gets a bigger budget — the previous attempt exhausted the old one. */
|
|
17239
|
+
raiseBudgetForResume(maxTokens) {
|
|
17240
|
+
const raised = maxTokens ?? Math.round((this.config.budget?.maxTokensPerRun ?? 2e5) * 2);
|
|
15943
17241
|
this.config = { ...this.config, budget: { ...this.config.budget, maxTokensPerRun: raised } };
|
|
15944
17242
|
this.router.setMaxTokensPerRun(raised);
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
17243
|
+
}
|
|
17244
|
+
/**
|
|
17245
|
+
* The resume prompt. Completed sections are named explicitly rather than
|
|
17246
|
+
* gestured at: "do not recreate the files" is an instruction a planner can
|
|
17247
|
+
* only guess at, whereas a list of what is already done, and what it produced,
|
|
17248
|
+
* is something it can act on. That difference is the entire reason node
|
|
17249
|
+
* results are kept — a re-plan that cannot see the finished work will plan the
|
|
17250
|
+
* finished work again, and the user pays for it a second time.
|
|
17251
|
+
*/
|
|
17252
|
+
buildResumePrompt(prompt, partialOutput, completed) {
|
|
17253
|
+
const done = summarizeCompleted(completed);
|
|
17254
|
+
return [
|
|
17255
|
+
"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.",
|
|
17256
|
+
"",
|
|
17257
|
+
`Original task: ${prompt}`,
|
|
17258
|
+
done ? `
|
|
17259
|
+
${done}` : "",
|
|
17260
|
+
partialOutput ? `
|
|
15949
17261
|
Partial result so far:
|
|
15950
|
-
${
|
|
17262
|
+
${partialOutput}` : ""
|
|
17263
|
+
].filter(Boolean).join("\n");
|
|
15951
17264
|
}
|
|
15952
17265
|
/**
|
|
15953
17266
|
* Resume the last budget-capped task with a raised budget (SDK/headless).
|
|
15954
17267
|
* Returns null when there is nothing to resume.
|
|
15955
17268
|
*/
|
|
15956
17269
|
async resumeRun(opts = {}) {
|
|
15957
|
-
const prompt = this.prepareResume(opts);
|
|
17270
|
+
const prompt = await this.prepareDurableResume(opts) ?? this.prepareResume(opts);
|
|
15958
17271
|
if (!prompt) return null;
|
|
15959
17272
|
return this.run({ prompt });
|
|
15960
17273
|
}
|
|
@@ -16325,6 +17638,7 @@ ${prompt}` : prompt;
|
|
|
16325
17638
|
const startMs = Date.now();
|
|
16326
17639
|
const taskId = randomUUID();
|
|
16327
17640
|
this.decisionLog = [];
|
|
17641
|
+
this.currentRunCompleted = [];
|
|
16328
17642
|
if (options.fastAnswer) {
|
|
16329
17643
|
return this.runFastAnswer(options, startMs, taskId);
|
|
16330
17644
|
}
|
|
@@ -16439,6 +17753,11 @@ ${prompt}` : prompt;
|
|
|
16439
17753
|
});
|
|
16440
17754
|
tier.on("log", (e) => this.emit("log", e));
|
|
16441
17755
|
tier.on("tier:status", (e) => this.emit("tier:status", e));
|
|
17756
|
+
tier.on("section:complete", (e) => {
|
|
17757
|
+
if (e.status !== "COMPLETED" && e.status !== "PARTIAL") return;
|
|
17758
|
+
this.currentRunCompleted.push(e);
|
|
17759
|
+
this.emit("section:complete", e);
|
|
17760
|
+
});
|
|
16442
17761
|
tier.on("tool:call", (e) => this.emit("tool:call", e));
|
|
16443
17762
|
tier.on("tool:result", (e) => this.emit("tool:result", e));
|
|
16444
17763
|
tier.on("tool:approval-request", async (request) => {
|
|
@@ -16585,6 +17904,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
16585
17904
|
if (budgetInfo) throw new CascadeRouter.BudgetExceededError(budgetInfo.reason);
|
|
16586
17905
|
const trip = runBreaker.reason();
|
|
16587
17906
|
if (trip) {
|
|
17907
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "breaker", trip.message);
|
|
16588
17908
|
finalOutput = [
|
|
16589
17909
|
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
16590
17910
|
"",
|
|
@@ -16604,6 +17924,13 @@ ${finalOutput}` : ""
|
|
|
16604
17924
|
reason: err instanceof Error ? err.message : "Task cancelled",
|
|
16605
17925
|
partialOutput: finalOutput || ""
|
|
16606
17926
|
});
|
|
17927
|
+
await this.checkpointRun(
|
|
17928
|
+
taskId,
|
|
17929
|
+
options.prompt,
|
|
17930
|
+
finalOutput || "",
|
|
17931
|
+
"cancelled",
|
|
17932
|
+
err instanceof Error ? err.message : "Task cancelled"
|
|
17933
|
+
);
|
|
16607
17934
|
runError = null;
|
|
16608
17935
|
} else if (err instanceof Error && err.name === "BudgetExceededError") {
|
|
16609
17936
|
this.emit("run:budget-exceeded", {
|
|
@@ -16612,9 +17939,17 @@ ${finalOutput}` : ""
|
|
|
16612
17939
|
partialOutput: finalOutput || ""
|
|
16613
17940
|
});
|
|
16614
17941
|
this.lastInterruptedRun = { prompt: options.prompt, partialOutput: finalOutput || "", taskId };
|
|
17942
|
+
await this.checkpointRun(taskId, options.prompt, finalOutput || "", "budget", err.message);
|
|
16615
17943
|
if (!finalOutput) finalOutput = `\u26A0 Stopped to avoid runaway cost: ${err.message}`;
|
|
16616
17944
|
runError = null;
|
|
16617
17945
|
} else {
|
|
17946
|
+
await this.checkpointRun(
|
|
17947
|
+
taskId,
|
|
17948
|
+
options.prompt,
|
|
17949
|
+
finalOutput || "",
|
|
17950
|
+
"error",
|
|
17951
|
+
err instanceof Error ? err.message : String(err)
|
|
17952
|
+
);
|
|
16618
17953
|
runError = err;
|
|
16619
17954
|
throw err;
|
|
16620
17955
|
}
|
|
@@ -16862,7 +18197,7 @@ var Keystore = class {
|
|
|
16862
18197
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
16863
18198
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
16864
18199
|
this.backend = "keytar";
|
|
16865
|
-
if (password &&
|
|
18200
|
+
if (password && fs25.existsSync(this.storePath)) {
|
|
16866
18201
|
try {
|
|
16867
18202
|
const fileEntries = this.decryptFile(password);
|
|
16868
18203
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -16881,7 +18216,7 @@ var Keystore = class {
|
|
|
16881
18216
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
16882
18217
|
);
|
|
16883
18218
|
}
|
|
16884
|
-
if (!
|
|
18219
|
+
if (!fs25.existsSync(this.storePath)) {
|
|
16885
18220
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16886
18221
|
this.masterKey = this.deriveKey(password, salt);
|
|
16887
18222
|
this.writeWithSalt({}, salt);
|
|
@@ -16895,7 +18230,7 @@ var Keystore = class {
|
|
|
16895
18230
|
}
|
|
16896
18231
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
16897
18232
|
unlockSync(password) {
|
|
16898
|
-
if (!
|
|
18233
|
+
if (!fs25.existsSync(this.storePath)) {
|
|
16899
18234
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16900
18235
|
this.masterKey = this.deriveKey(password, salt);
|
|
16901
18236
|
this.writeWithSalt({}, salt);
|
|
@@ -16953,7 +18288,7 @@ var Keystore = class {
|
|
|
16953
18288
|
}
|
|
16954
18289
|
}
|
|
16955
18290
|
decryptFile(password, knownSalt) {
|
|
16956
|
-
if (!
|
|
18291
|
+
if (!fs25.existsSync(this.storePath)) return {};
|
|
16957
18292
|
try {
|
|
16958
18293
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
16959
18294
|
const useSalt = knownSalt ?? salt;
|
|
@@ -16975,8 +18310,8 @@ var Keystore = class {
|
|
|
16975
18310
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16976
18311
|
const tag = cipher.getAuthTag();
|
|
16977
18312
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
16978
|
-
|
|
16979
|
-
|
|
18313
|
+
fs25.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
18314
|
+
fs25.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16980
18315
|
}
|
|
16981
18316
|
writeWithSalt(data, salt) {
|
|
16982
18317
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -16986,11 +18321,11 @@ var Keystore = class {
|
|
|
16986
18321
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16987
18322
|
const tag = cipher.getAuthTag();
|
|
16988
18323
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
16989
|
-
|
|
16990
|
-
|
|
18324
|
+
fs25.mkdirSync(path26.dirname(this.storePath), { recursive: true });
|
|
18325
|
+
fs25.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16991
18326
|
}
|
|
16992
18327
|
readRaw() {
|
|
16993
|
-
const buf =
|
|
18328
|
+
const buf = fs25.readFileSync(this.storePath);
|
|
16994
18329
|
let offset = 0;
|
|
16995
18330
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
16996
18331
|
offset += SALT_LEN;
|
|
@@ -17006,9 +18341,9 @@ var Keystore = class {
|
|
|
17006
18341
|
}
|
|
17007
18342
|
};
|
|
17008
18343
|
async function loadCascadeMd(workspacePath) {
|
|
17009
|
-
const filePath =
|
|
18344
|
+
const filePath = path26.join(workspacePath, "CASCADE.md");
|
|
17010
18345
|
try {
|
|
17011
|
-
const raw = await
|
|
18346
|
+
const raw = await fs5.readFile(filePath, "utf-8");
|
|
17012
18347
|
return parseCascadeMd(raw);
|
|
17013
18348
|
} catch {
|
|
17014
18349
|
return null;
|
|
@@ -17037,7 +18372,7 @@ ${raw.trim()}`;
|
|
|
17037
18372
|
var MemoryStore = class _MemoryStore {
|
|
17038
18373
|
db;
|
|
17039
18374
|
constructor(dbPath) {
|
|
17040
|
-
|
|
18375
|
+
fs25.mkdirSync(path26.dirname(dbPath), { recursive: true });
|
|
17041
18376
|
try {
|
|
17042
18377
|
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
17043
18378
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17873,7 +19208,7 @@ Original error: ${err.message}`
|
|
|
17873
19208
|
}
|
|
17874
19209
|
};
|
|
17875
19210
|
function credentialsPath(globalDir2) {
|
|
17876
|
-
return
|
|
19211
|
+
return path26.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
17877
19212
|
}
|
|
17878
19213
|
function providerKey(p) {
|
|
17879
19214
|
if (p.type === "azure") {
|
|
@@ -17886,7 +19221,7 @@ function isPersistable(p) {
|
|
|
17886
19221
|
}
|
|
17887
19222
|
function loadGlobalCredentials(globalDir2) {
|
|
17888
19223
|
try {
|
|
17889
|
-
const raw =
|
|
19224
|
+
const raw = fs25.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
17890
19225
|
const parsed = JSON.parse(raw);
|
|
17891
19226
|
if (!Array.isArray(parsed.providers)) return [];
|
|
17892
19227
|
return parsed.providers.filter(
|
|
@@ -17898,13 +19233,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
17898
19233
|
}
|
|
17899
19234
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
17900
19235
|
const filePath = credentialsPath(globalDir2);
|
|
17901
|
-
|
|
19236
|
+
fs25.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
17902
19237
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
17903
19238
|
const tmp = `${filePath}.tmp`;
|
|
17904
|
-
|
|
17905
|
-
|
|
19239
|
+
fs25.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
19240
|
+
fs25.renameSync(tmp, filePath);
|
|
17906
19241
|
try {
|
|
17907
|
-
|
|
19242
|
+
fs25.chmodSync(filePath, 384);
|
|
17908
19243
|
} catch {
|
|
17909
19244
|
}
|
|
17910
19245
|
}
|
|
@@ -17939,7 +19274,7 @@ var ConfigManager = class {
|
|
|
17939
19274
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
17940
19275
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
17941
19276
|
this.workspacePath = workspacePath;
|
|
17942
|
-
this.globalDir = globalDirOverride ??
|
|
19277
|
+
this.globalDir = globalDirOverride ?? path26.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
17943
19278
|
}
|
|
17944
19279
|
async load() {
|
|
17945
19280
|
this.config = await this.loadConfig();
|
|
@@ -17953,8 +19288,8 @@ var ConfigManager = class {
|
|
|
17953
19288
|
this.ignore = new CascadeIgnore();
|
|
17954
19289
|
await this.ignore.load(this.workspacePath);
|
|
17955
19290
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
17956
|
-
this.keystore = new Keystore(
|
|
17957
|
-
this.store = new MemoryStore(
|
|
19291
|
+
this.keystore = new Keystore(path26.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
19292
|
+
this.store = new MemoryStore(path26.join(this.workspacePath, CASCADE_DB_FILE));
|
|
17958
19293
|
await this.injectEnvKeys();
|
|
17959
19294
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
17960
19295
|
await this.ensureDefaultIdentity();
|
|
@@ -17979,9 +19314,9 @@ var ConfigManager = class {
|
|
|
17979
19314
|
return this.workspacePath;
|
|
17980
19315
|
}
|
|
17981
19316
|
async save() {
|
|
17982
|
-
const configPath =
|
|
17983
|
-
await
|
|
17984
|
-
await
|
|
19317
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19318
|
+
await fs5.mkdir(path26.dirname(configPath), { recursive: true });
|
|
19319
|
+
await fs5.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
17985
19320
|
try {
|
|
17986
19321
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
17987
19322
|
} catch (err) {
|
|
@@ -18016,9 +19351,9 @@ var ConfigManager = class {
|
|
|
18016
19351
|
return configProvider?.apiKey;
|
|
18017
19352
|
}
|
|
18018
19353
|
async loadConfig() {
|
|
18019
|
-
const configPath =
|
|
19354
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18020
19355
|
try {
|
|
18021
|
-
const raw = await
|
|
19356
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
18022
19357
|
return validateConfig(JSON.parse(raw));
|
|
18023
19358
|
} catch (err) {
|
|
18024
19359
|
if (err.code === "ENOENT") {
|
|
@@ -18548,7 +19883,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
18548
19883
|
}
|
|
18549
19884
|
|
|
18550
19885
|
// src/dashboard/server.ts
|
|
18551
|
-
var __dirname$1 =
|
|
19886
|
+
var __dirname$1 = path26.dirname(fileURLToPath(import.meta.url));
|
|
18552
19887
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
18553
19888
|
var DashboardServer = class {
|
|
18554
19889
|
app;
|
|
@@ -18828,14 +20163,14 @@ var DashboardServer = class {
|
|
|
18828
20163
|
*/
|
|
18829
20164
|
persistConfig() {
|
|
18830
20165
|
try {
|
|
18831
|
-
const configPath =
|
|
18832
|
-
|
|
18833
|
-
|
|
20166
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20167
|
+
fs25.mkdirSync(path26.dirname(configPath), { recursive: true });
|
|
20168
|
+
fs25.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18834
20169
|
} catch (err) {
|
|
18835
20170
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18836
20171
|
}
|
|
18837
20172
|
try {
|
|
18838
|
-
saveGlobalCredentials(
|
|
20173
|
+
saveGlobalCredentials(path26.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
18839
20174
|
} catch (err) {
|
|
18840
20175
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
18841
20176
|
}
|
|
@@ -18850,15 +20185,15 @@ var DashboardServer = class {
|
|
|
18850
20185
|
resolveDashboardSecret() {
|
|
18851
20186
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
18852
20187
|
if (fromConfig) return fromConfig;
|
|
18853
|
-
const secretPath =
|
|
20188
|
+
const secretPath = path26.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
18854
20189
|
try {
|
|
18855
|
-
if (
|
|
18856
|
-
const existing =
|
|
20190
|
+
if (fs25.existsSync(secretPath)) {
|
|
20191
|
+
const existing = fs25.readFileSync(secretPath, "utf-8").trim();
|
|
18857
20192
|
if (existing.length >= 16) return existing;
|
|
18858
20193
|
}
|
|
18859
20194
|
const generated = randomUUID();
|
|
18860
|
-
|
|
18861
|
-
|
|
20195
|
+
fs25.mkdirSync(path26.dirname(secretPath), { recursive: true });
|
|
20196
|
+
fs25.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
18862
20197
|
if (this.config.dashboard.auth) {
|
|
18863
20198
|
console.warn(
|
|
18864
20199
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -18885,7 +20220,7 @@ var DashboardServer = class {
|
|
|
18885
20220
|
// ── Setup ─────────────────────────────────────
|
|
18886
20221
|
getGlobalStore() {
|
|
18887
20222
|
if (!this.globalStore) {
|
|
18888
|
-
const globalDbPath =
|
|
20223
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
18889
20224
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
18890
20225
|
}
|
|
18891
20226
|
return this.globalStore;
|
|
@@ -19265,12 +20600,12 @@ ${prompt}`;
|
|
|
19265
20600
|
}
|
|
19266
20601
|
}
|
|
19267
20602
|
watchRuntimeChanges() {
|
|
19268
|
-
const workspaceDbPath =
|
|
19269
|
-
const globalDbPath =
|
|
20603
|
+
const workspaceDbPath = path26.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20604
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19270
20605
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
19271
20606
|
for (const watchPath of watchPaths) {
|
|
19272
|
-
if (!
|
|
19273
|
-
|
|
20607
|
+
if (!fs25.existsSync(watchPath)) continue;
|
|
20608
|
+
fs25.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
19274
20609
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
19275
20610
|
});
|
|
19276
20611
|
}
|
|
@@ -19392,7 +20727,7 @@ ${prompt}`;
|
|
|
19392
20727
|
const sessionId = req.params.id;
|
|
19393
20728
|
this.store.deleteSession(sessionId);
|
|
19394
20729
|
this.store.deleteRuntimeSession(sessionId);
|
|
19395
|
-
const globalDbPath =
|
|
20730
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19396
20731
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19397
20732
|
try {
|
|
19398
20733
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -19485,7 +20820,7 @@ ${prompt}`;
|
|
|
19485
20820
|
});
|
|
19486
20821
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
19487
20822
|
const body = req.body;
|
|
19488
|
-
const globalDbPath =
|
|
20823
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19489
20824
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
19490
20825
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19491
20826
|
try {
|
|
@@ -19508,7 +20843,7 @@ ${prompt}`;
|
|
|
19508
20843
|
});
|
|
19509
20844
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
19510
20845
|
this.store.deleteAllRuntimeNodes();
|
|
19511
|
-
const globalDbPath =
|
|
20846
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19512
20847
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19513
20848
|
try {
|
|
19514
20849
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -19594,12 +20929,12 @@ ${prompt}`;
|
|
|
19594
20929
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
19595
20930
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
19596
20931
|
try {
|
|
19597
|
-
const configPath =
|
|
19598
|
-
const existing =
|
|
20932
|
+
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20933
|
+
const existing = fs25.existsSync(configPath) ? JSON.parse(fs25.readFileSync(configPath, "utf-8")) : {};
|
|
19599
20934
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
19600
20935
|
const tmp = configPath + ".tmp";
|
|
19601
|
-
|
|
19602
|
-
|
|
20936
|
+
fs25.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20937
|
+
fs25.renameSync(tmp, configPath);
|
|
19603
20938
|
res.json({ ok: true });
|
|
19604
20939
|
} catch (err) {
|
|
19605
20940
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -19627,7 +20962,7 @@ ${prompt}`;
|
|
|
19627
20962
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
19628
20963
|
const scope = req.query["scope"] ?? "workspace";
|
|
19629
20964
|
if (scope === "global") {
|
|
19630
|
-
const globalDbPath =
|
|
20965
|
+
const globalDbPath = path26.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19631
20966
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19632
20967
|
try {
|
|
19633
20968
|
res.json({
|
|
@@ -19968,13 +21303,13 @@ ${prompt}`;
|
|
|
19968
21303
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
19969
21304
|
}
|
|
19970
21305
|
});
|
|
19971
|
-
const prodPath =
|
|
19972
|
-
const devPath =
|
|
19973
|
-
const webDistPath =
|
|
19974
|
-
if (
|
|
21306
|
+
const prodPath = path26.resolve(__dirname$1, "../web/dist");
|
|
21307
|
+
const devPath = path26.resolve(__dirname$1, "../../web/dist");
|
|
21308
|
+
const webDistPath = fs25.existsSync(prodPath) ? prodPath : devPath;
|
|
21309
|
+
if (fs25.existsSync(webDistPath)) {
|
|
19975
21310
|
this.app.use(express.static(webDistPath));
|
|
19976
21311
|
this.app.get("*", (_req, res) => {
|
|
19977
|
-
res.sendFile(
|
|
21312
|
+
res.sendFile(path26.join(webDistPath, "index.html"));
|
|
19978
21313
|
});
|
|
19979
21314
|
} else {
|
|
19980
21315
|
this.app.get("/", (_req, res) => {
|
|
@@ -20161,14 +21496,14 @@ function planRetrieval(ctx) {
|
|
|
20161
21496
|
}
|
|
20162
21497
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20163
21498
|
function globalDir(homeDir = os6.homedir()) {
|
|
20164
|
-
return
|
|
21499
|
+
return path26.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20165
21500
|
}
|
|
20166
21501
|
function cloudSessionPath(dir = globalDir()) {
|
|
20167
|
-
return
|
|
21502
|
+
return path26.join(dir, CLOUD_SESSION_FILE);
|
|
20168
21503
|
}
|
|
20169
21504
|
function loadCloudSession(dir = globalDir()) {
|
|
20170
21505
|
try {
|
|
20171
|
-
const raw =
|
|
21506
|
+
const raw = fs25.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20172
21507
|
const parsed = JSON.parse(raw);
|
|
20173
21508
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20174
21509
|
return parsed;
|
|
@@ -20177,17 +21512,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20177
21512
|
}
|
|
20178
21513
|
}
|
|
20179
21514
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20180
|
-
|
|
21515
|
+
fs25.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20181
21516
|
const file = cloudSessionPath(dir);
|
|
20182
|
-
|
|
21517
|
+
fs25.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20183
21518
|
try {
|
|
20184
|
-
|
|
21519
|
+
fs25.chmodSync(file, 384);
|
|
20185
21520
|
} catch {
|
|
20186
21521
|
}
|
|
20187
21522
|
}
|
|
20188
21523
|
function clearCloudSession(dir = globalDir()) {
|
|
20189
21524
|
try {
|
|
20190
|
-
|
|
21525
|
+
fs25.rmSync(cloudSessionPath(dir));
|
|
20191
21526
|
} catch {
|
|
20192
21527
|
}
|
|
20193
21528
|
}
|
|
@@ -20359,16 +21694,16 @@ var CloudClient = class _CloudClient {
|
|
|
20359
21694
|
return (await this.refresh()).accessToken;
|
|
20360
21695
|
}
|
|
20361
21696
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20362
|
-
async authed(
|
|
20363
|
-
const call = (token) => fetch(this.url(
|
|
21697
|
+
async authed(path28, init = {}) {
|
|
21698
|
+
const call = (token) => fetch(this.url(path28), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20364
21699
|
let res = await call(await this.accessToken());
|
|
20365
21700
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20366
21701
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20367
21702
|
const text = await res.text();
|
|
20368
21703
|
return text ? JSON.parse(text) : {};
|
|
20369
21704
|
}
|
|
20370
|
-
authedGet(
|
|
20371
|
-
return this.authed(
|
|
21705
|
+
authedGet(path28) {
|
|
21706
|
+
return this.authed(path28);
|
|
20372
21707
|
}
|
|
20373
21708
|
async fetchMe(token) {
|
|
20374
21709
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20705,6 +22040,6 @@ function applySyncBundle(bundle, config) {
|
|
|
20705
22040
|
return next;
|
|
20706
22041
|
}
|
|
20707
22042
|
|
|
20708
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, isMcpToolName, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, runCascade, sessionWorthRemembering, streamCascade, uniqueMcpServerName };
|
|
22043
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, evaluateAcceptance, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
20709
22044
|
//# sourceMappingURL=index.js.map
|
|
20710
22045
|
//# sourceMappingURL=index.js.map
|