pi-studio 0.9.17 → 0.9.19
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/CHANGELOG.md +23 -0
- package/README.md +4 -4
- package/client/studio-client.js +945 -102
- package/client/studio.css +190 -3
- package/index.ts +394 -56
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -162,6 +162,7 @@ interface PreparedStudioPdfExport {
|
|
|
162
162
|
createdAt: number;
|
|
163
163
|
filePath?: string;
|
|
164
164
|
tempDirPath?: string;
|
|
165
|
+
persistent?: boolean;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
interface PreparedStudioHtmlExport {
|
|
@@ -171,6 +172,7 @@ interface PreparedStudioHtmlExport {
|
|
|
171
172
|
createdAt: number;
|
|
172
173
|
filePath?: string;
|
|
173
174
|
tempDirPath?: string;
|
|
175
|
+
persistent?: boolean;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
interface StudioHtmlAnnotationPlaceholder {
|
|
@@ -255,6 +257,7 @@ interface StudioTraceToolEntry {
|
|
|
255
257
|
toolName: string;
|
|
256
258
|
label: string | null;
|
|
257
259
|
argsSummary: string | null;
|
|
260
|
+
args: string | null;
|
|
258
261
|
output: string;
|
|
259
262
|
images: StudioTraceImage[];
|
|
260
263
|
startedAt: number;
|
|
@@ -427,6 +430,7 @@ interface SaveOverRequestMessage {
|
|
|
427
430
|
interface RefreshFromDiskRequestMessage {
|
|
428
431
|
type: "refresh_from_disk_request";
|
|
429
432
|
requestId: string;
|
|
433
|
+
path?: string;
|
|
430
434
|
}
|
|
431
435
|
|
|
432
436
|
interface SendToEditorRequestMessage {
|
|
@@ -515,6 +519,7 @@ const MAX_PREPARED_PDF_EXPORTS = 8;
|
|
|
515
519
|
const MAX_PREPARED_HTML_EXPORTS = 8;
|
|
516
520
|
const STUDIO_TRACE_SNAPSHOT_MAX_ENTRIES = 80;
|
|
517
521
|
const STUDIO_TRACE_SNAPSHOT_MAX_FIELD_CHARS = 20_000;
|
|
522
|
+
const STUDIO_TRACE_TOOL_ARGS_MAX_CHARS = 20_000;
|
|
518
523
|
const STUDIO_TRACE_IMAGE_MAX_COUNT = 8;
|
|
519
524
|
const STUDIO_TRACE_IMAGE_MAX_BASE64_CHARS = 2_500_000;
|
|
520
525
|
const STUDIO_TRACE_SNAPSHOT_MAX_IMAGES = 12;
|
|
@@ -2257,6 +2262,8 @@ function inferStudioPdfLanguageFromPath(pathInput: string): string | undefined {
|
|
|
2257
2262
|
".yml": "yaml",
|
|
2258
2263
|
".toml": "toml",
|
|
2259
2264
|
".lua": "lua",
|
|
2265
|
+
".csv": "csv",
|
|
2266
|
+
".tsv": "tsv",
|
|
2260
2267
|
".txt": "text",
|
|
2261
2268
|
".rst": "text",
|
|
2262
2269
|
".adoc": "text",
|
|
@@ -2299,6 +2306,33 @@ function buildStudioResponseExportOutputPath(cwd: string, extension: "pdf" | "ht
|
|
|
2299
2306
|
return join(cwd || process.cwd(), `studio-response-${formatStudioExportTimestamp()}.studio.${extension}`);
|
|
2300
2307
|
}
|
|
2301
2308
|
|
|
2309
|
+
function buildStudioPreviewExportPath(sourcePath: string | undefined, resourceDir: string | undefined, fallbackCwd: string, filename: string): string | null {
|
|
2310
|
+
const cleanFilename = String(filename || "").trim();
|
|
2311
|
+
if (!cleanFilename) return null;
|
|
2312
|
+
const source = typeof sourcePath === "string" ? sourcePath.trim() : "";
|
|
2313
|
+
if (source) {
|
|
2314
|
+
const expanded = recoverLikelyDroppedLeadingSlashPath(expandHome(source));
|
|
2315
|
+
return join(dirname(isAbsolute(expanded) ? expanded : resolve(fallbackCwd, expanded)), cleanFilename);
|
|
2316
|
+
}
|
|
2317
|
+
const resource = normalizeStudioResourceDirectoryInput(typeof resourceDir === "string" ? resourceDir : "");
|
|
2318
|
+
if (resource) {
|
|
2319
|
+
const expanded = recoverLikelyDroppedLeadingSlashPath(expandHome(resource));
|
|
2320
|
+
return join(isAbsolute(expanded) ? expanded : resolve(fallbackCwd, expanded), cleanFilename);
|
|
2321
|
+
}
|
|
2322
|
+
return join(fallbackCwd || process.cwd(), cleanFilename);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function writeStudioPreviewExportFile(path: string | null, data: Buffer): { filePath: string | null; error: string | null } {
|
|
2326
|
+
if (!path) return { filePath: null, error: "No export path was resolved." };
|
|
2327
|
+
try {
|
|
2328
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
2329
|
+
writeFileSync(path, data);
|
|
2330
|
+
return { filePath: path, error: null };
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
return { filePath: null, error: error instanceof Error ? error.message : String(error) };
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2302
2336
|
function writeStudioFile(pathArg: string, cwd: string, content: string):
|
|
2303
2337
|
| { ok: true; label: string; resolvedPath: string }
|
|
2304
2338
|
| { ok: false; message: string } {
|
|
@@ -2466,6 +2500,7 @@ const STUDIO_LOCAL_LINK_TEXT_EXTENSIONS = new Set([
|
|
|
2466
2500
|
".diff", ".patch",
|
|
2467
2501
|
]);
|
|
2468
2502
|
const STUDIO_LOCAL_LINK_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
2503
|
+
const STUDIO_LOCAL_LINK_OFFICE_EXTENSIONS = new Set([".docx", ".odt"]);
|
|
2469
2504
|
const STUDIO_LOCAL_LINK_TEXT_FILENAMES = new Set([
|
|
2470
2505
|
".dockerignore", ".editorconfig", ".env", ".env.example", ".eslintignore", ".gitattributes",
|
|
2471
2506
|
".gitignore", ".gitmodules", ".npmignore", ".prettierignore", "dockerfile", "gemfile",
|
|
@@ -2477,7 +2512,7 @@ const STUDIO_FILE_BROWSER_IGNORED_DIRS = new Set([
|
|
|
2477
2512
|
"__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".ruff_cache",
|
|
2478
2513
|
]);
|
|
2479
2514
|
|
|
2480
|
-
type StudioLocalPreviewResourceKind = "pdf" | "text" | "image" | "other";
|
|
2515
|
+
type StudioLocalPreviewResourceKind = "pdf" | "text" | "image" | "office" | "other";
|
|
2481
2516
|
|
|
2482
2517
|
interface StudioLocalPreviewResource {
|
|
2483
2518
|
filePath: string;
|
|
@@ -2573,6 +2608,7 @@ function getStudioLocalPreviewResourceKind(extension: string, filePathOrName?: s
|
|
|
2573
2608
|
if (ext === ".pdf") return "pdf";
|
|
2574
2609
|
if (STUDIO_LOCAL_LINK_TEXT_EXTENSIONS.has(ext) || STUDIO_LOCAL_LINK_TEXT_FILENAMES.has(name)) return "text";
|
|
2575
2610
|
if (STUDIO_LOCAL_LINK_IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
2611
|
+
if (STUDIO_LOCAL_LINK_OFFICE_EXTENSIONS.has(ext)) return "office";
|
|
2576
2612
|
return "other";
|
|
2577
2613
|
}
|
|
2578
2614
|
|
|
@@ -4304,6 +4340,125 @@ function wrapStudioCodeAsMarkdown(code: string, language?: string): string {
|
|
|
4304
4340
|
return `${marker}${lang}\n${source}\n${marker}`;
|
|
4305
4341
|
}
|
|
4306
4342
|
|
|
4343
|
+
const STUDIO_DELIMITED_PREVIEW_MAX_DATA_ROWS = 200;
|
|
4344
|
+
const STUDIO_DELIMITED_PREVIEW_MAX_COLUMNS = 50;
|
|
4345
|
+
const STUDIO_DELIMITED_PREVIEW_MAX_CELL_CHARS = 500;
|
|
4346
|
+
|
|
4347
|
+
function getStudioDelimitedTextConfig(language?: string): { label: string; delimiter: string } | null {
|
|
4348
|
+
const normalized = normalizeStudioEditorLanguage(language);
|
|
4349
|
+
if (normalized === "csv") return { label: "CSV", delimiter: "," };
|
|
4350
|
+
if (normalized === "tsv") return { label: "TSV", delimiter: "\t" };
|
|
4351
|
+
return null;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
function parseStudioDelimitedTextRows(text: string, delimiter: string, maxRows: number): { rows: string[][]; truncatedRows: boolean } {
|
|
4355
|
+
const source = String(text ?? "").replace(/^\uFEFF/, "");
|
|
4356
|
+
const limit = Math.max(1, Math.floor(maxRows));
|
|
4357
|
+
const rows: string[][] = [];
|
|
4358
|
+
let row: string[] = [];
|
|
4359
|
+
let cell = "";
|
|
4360
|
+
let inQuotes = false;
|
|
4361
|
+
let truncatedRows = false;
|
|
4362
|
+
|
|
4363
|
+
const pushCell = () => {
|
|
4364
|
+
row.push(cell);
|
|
4365
|
+
cell = "";
|
|
4366
|
+
};
|
|
4367
|
+
const pushRow = (index: number): boolean => {
|
|
4368
|
+
pushCell();
|
|
4369
|
+
rows.push(row);
|
|
4370
|
+
row = [];
|
|
4371
|
+
if (rows.length >= limit) {
|
|
4372
|
+
truncatedRows = index < source.length - 1;
|
|
4373
|
+
return true;
|
|
4374
|
+
}
|
|
4375
|
+
return false;
|
|
4376
|
+
};
|
|
4377
|
+
|
|
4378
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
4379
|
+
if (rows.length >= limit) {
|
|
4380
|
+
truncatedRows = true;
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
const ch = source[i];
|
|
4384
|
+
if (inQuotes) {
|
|
4385
|
+
if (ch === '"') {
|
|
4386
|
+
if (source[i + 1] === '"') {
|
|
4387
|
+
cell += '"';
|
|
4388
|
+
i += 1;
|
|
4389
|
+
} else {
|
|
4390
|
+
inQuotes = false;
|
|
4391
|
+
}
|
|
4392
|
+
} else {
|
|
4393
|
+
cell += ch;
|
|
4394
|
+
}
|
|
4395
|
+
continue;
|
|
4396
|
+
}
|
|
4397
|
+
if (ch === '"' && cell === "") {
|
|
4398
|
+
inQuotes = true;
|
|
4399
|
+
continue;
|
|
4400
|
+
}
|
|
4401
|
+
if (ch === delimiter) {
|
|
4402
|
+
pushCell();
|
|
4403
|
+
continue;
|
|
4404
|
+
}
|
|
4405
|
+
if (ch === "\n") {
|
|
4406
|
+
if (pushRow(i)) break;
|
|
4407
|
+
continue;
|
|
4408
|
+
}
|
|
4409
|
+
if (ch === "\r") {
|
|
4410
|
+
if (source[i + 1] === "\n") i += 1;
|
|
4411
|
+
if (pushRow(i)) break;
|
|
4412
|
+
continue;
|
|
4413
|
+
}
|
|
4414
|
+
cell += ch;
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
if (!truncatedRows && rows.length < limit && (cell.length > 0 || row.length > 0)) {
|
|
4418
|
+
pushCell();
|
|
4419
|
+
rows.push(row);
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
return { rows, truncatedRows };
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
function formatStudioDelimitedMarkdownCell(value: string | undefined): string {
|
|
4426
|
+
const raw = String(value ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4427
|
+
const shortened = raw.length > STUDIO_DELIMITED_PREVIEW_MAX_CELL_CHARS
|
|
4428
|
+
? `${raw.slice(0, STUDIO_DELIMITED_PREVIEW_MAX_CELL_CHARS)}…`
|
|
4429
|
+
: raw;
|
|
4430
|
+
return shortened.replace(/\n/g, "<br>").replace(/\|/g, "\\|").trim() || " ";
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
function formatStudioDelimitedTextAsMarkdown(text: string, language?: string): string | null {
|
|
4434
|
+
const config = getStudioDelimitedTextConfig(language);
|
|
4435
|
+
if (!config) return null;
|
|
4436
|
+
const parsed = parseStudioDelimitedTextRows(text, config.delimiter, STUDIO_DELIMITED_PREVIEW_MAX_DATA_ROWS + 1);
|
|
4437
|
+
const rows = parsed.rows;
|
|
4438
|
+
if (!rows.length) return `_${config.label} file has no tabular data to preview._`;
|
|
4439
|
+
const rawColumnCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
|
|
4440
|
+
const columnCount = Math.min(rawColumnCount, STUDIO_DELIMITED_PREVIEW_MAX_COLUMNS);
|
|
4441
|
+
if (columnCount <= 0) return `_${config.label} file has no tabular data to preview._`;
|
|
4442
|
+
const header = rows[0] ?? [];
|
|
4443
|
+
const dataRows = rows.slice(1);
|
|
4444
|
+
const columnIndexes = Array.from({ length: columnCount }, (_value, index) => index);
|
|
4445
|
+
const lines: string[] = [`**${config.label} preview**`, ""];
|
|
4446
|
+
const notices: string[] = [];
|
|
4447
|
+
if (parsed.truncatedRows) notices.push(`showing first ${Math.max(0, dataRows.length)} data rows`);
|
|
4448
|
+
if (rawColumnCount > columnCount) notices.push(`showing first ${columnCount} of ${rawColumnCount} columns`);
|
|
4449
|
+
if (notices.length) lines.push(`_${notices.join("; ")}._`, "");
|
|
4450
|
+
lines.push(`| ${columnIndexes.map((index) => formatStudioDelimitedMarkdownCell(header[index] || `Column ${index + 1}`)).join(" | ")} |`);
|
|
4451
|
+
lines.push(`| ${columnIndexes.map(() => "---").join(" | ")} |`);
|
|
4452
|
+
if (dataRows.length) {
|
|
4453
|
+
dataRows.forEach((row) => {
|
|
4454
|
+
lines.push(`| ${columnIndexes.map((index) => formatStudioDelimitedMarkdownCell(row[index])).join(" | ")} |`);
|
|
4455
|
+
});
|
|
4456
|
+
} else {
|
|
4457
|
+
lines.push(`| ${columnIndexes.map(() => " ").join(" | ")} |`);
|
|
4458
|
+
}
|
|
4459
|
+
return lines.join("\n");
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4307
4462
|
function extractStudioFenceInfoLanguage(info: string): string | undefined {
|
|
4308
4463
|
const firstToken = String(info ?? "").trim().split(/\s+/)[0]?.replace(/^\./, "") ?? "";
|
|
4309
4464
|
return normalizeStudioEditorLanguage(firstToken || undefined);
|
|
@@ -5254,11 +5409,13 @@ function buildStudioLiteralTextPdfTexConfig(options?: StudioPdfRenderOptions): {
|
|
|
5254
5409
|
|
|
5255
5410
|
function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLanguage?: string): string {
|
|
5256
5411
|
if (isLatex) return markdown;
|
|
5257
|
-
const
|
|
5412
|
+
const delimitedMarkdown = formatStudioDelimitedTextAsMarkdown(markdown, editorLanguage);
|
|
5413
|
+
const input = delimitedMarkdown ?? markdown;
|
|
5414
|
+
const effectiveEditorLanguage = delimitedMarkdown ? "markdown" : inferStudioPdfLanguage(input, editorLanguage);
|
|
5258
5415
|
const source = effectiveEditorLanguage && effectiveEditorLanguage !== "markdown" && effectiveEditorLanguage !== "latex"
|
|
5259
|
-
&& !isStudioSingleFencedCodeBlock(
|
|
5260
|
-
? wrapStudioCodeAsMarkdown(
|
|
5261
|
-
:
|
|
5416
|
+
&& !isStudioSingleFencedCodeBlock(input)
|
|
5417
|
+
? wrapStudioCodeAsMarkdown(input, effectiveEditorLanguage)
|
|
5418
|
+
: input;
|
|
5262
5419
|
const annotationReadySource = !effectiveEditorLanguage || effectiveEditorLanguage === "markdown" || effectiveEditorLanguage === "latex"
|
|
5263
5420
|
? replaceStudioAnnotationMarkersForPdf(source)
|
|
5264
5421
|
: source;
|
|
@@ -5979,17 +6136,19 @@ async function renderStudioStandaloneHtmlWithPandoc(
|
|
|
5979
6136
|
sourcePath?: string,
|
|
5980
6137
|
options?: StudioHtmlRenderOptions,
|
|
5981
6138
|
): Promise<{ html: Buffer; warning?: string }> {
|
|
5982
|
-
const
|
|
5983
|
-
|
|
5984
|
-
|
|
6139
|
+
const delimitedMarkdown = isLatex ? null : formatStudioDelimitedTextAsMarkdown(markdown, editorLanguage);
|
|
6140
|
+
const input = delimitedMarkdown ?? markdown;
|
|
6141
|
+
const effectiveEditorLanguage = delimitedMarkdown ? "markdown" : inferStudioPdfLanguage(input, editorLanguage);
|
|
6142
|
+
if (!isLatex && isLikelyStandaloneStudioHtml(input, effectiveEditorLanguage)) {
|
|
6143
|
+
return { html: Buffer.from(String(input ?? ""), "utf-8") };
|
|
5985
6144
|
}
|
|
5986
6145
|
const source = !isLatex
|
|
5987
6146
|
&& effectiveEditorLanguage
|
|
5988
6147
|
&& effectiveEditorLanguage !== "markdown"
|
|
5989
6148
|
&& effectiveEditorLanguage !== "latex"
|
|
5990
|
-
&& !isStudioSingleFencedCodeBlock(
|
|
5991
|
-
? wrapStudioCodeAsMarkdown(
|
|
5992
|
-
:
|
|
6149
|
+
&& !isStudioSingleFencedCodeBlock(input)
|
|
6150
|
+
? wrapStudioCodeAsMarkdown(input, effectiveEditorLanguage)
|
|
6151
|
+
: input;
|
|
5993
6152
|
const annotationPrepared = prepareStudioAnnotationMarkersForHtml(source);
|
|
5994
6153
|
const pdfPrepared = prepareStudioPdfBlocksForHtml(annotationPrepared.markdown);
|
|
5995
6154
|
let renderedHtml = await renderStudioMarkdownWithPandoc(pdfPrepared.markdown, isLatex, resourcePath, sourcePath);
|
|
@@ -6660,7 +6819,74 @@ function respondHtmlPreviewResourceJson(req: IncomingMessage, res: ServerRespons
|
|
|
6660
6819
|
});
|
|
6661
6820
|
}
|
|
6662
6821
|
|
|
6663
|
-
function
|
|
6822
|
+
function formatStudioMarkdownAngleTarget(pathText: string): string {
|
|
6823
|
+
return `<${String(pathText || "").replace(/>/g, "%3E")}>`;
|
|
6824
|
+
}
|
|
6825
|
+
|
|
6826
|
+
function sanitizeStudioPreviewBlockLine(value: string): string {
|
|
6827
|
+
return String(value || "").replace(/[\r\n]+/g, " ").trim();
|
|
6828
|
+
}
|
|
6829
|
+
|
|
6830
|
+
function buildStudioLocalResourcePreviewDocument(resource: StudioLocalPreviewResource): InitialStudioDocument {
|
|
6831
|
+
const label = basename(resource.filePath) || resource.label || "local preview";
|
|
6832
|
+
const resourcePath = resource.label || basename(resource.filePath) || resource.filePath;
|
|
6833
|
+
const title = sanitizeStudioPreviewBlockLine(label);
|
|
6834
|
+
let text = "";
|
|
6835
|
+
if (resource.kind === "pdf") {
|
|
6836
|
+
text = "```studio-pdf\n"
|
|
6837
|
+
+ `path: ${sanitizeStudioPreviewBlockLine(resourcePath)}\n`
|
|
6838
|
+
+ `title: ${title || "PDF preview"}\n`
|
|
6839
|
+
+ "height: 820\n"
|
|
6840
|
+
+ "```\n";
|
|
6841
|
+
} else if (resource.kind === "image") {
|
|
6842
|
+
text = `})\n`;
|
|
6843
|
+
} else {
|
|
6844
|
+
throw new Error("This local resource cannot be opened as a preview document.");
|
|
6845
|
+
}
|
|
6846
|
+
return {
|
|
6847
|
+
text,
|
|
6848
|
+
label: `${label} preview`,
|
|
6849
|
+
source: "blank",
|
|
6850
|
+
resourceDir: resource.resourceDir,
|
|
6851
|
+
};
|
|
6852
|
+
}
|
|
6853
|
+
|
|
6854
|
+
function getStudioOfficePandocInputFormat(extension: string): string {
|
|
6855
|
+
const ext = String(extension || "").toLowerCase();
|
|
6856
|
+
if (ext === ".docx") return "docx";
|
|
6857
|
+
if (ext === ".odt") return "odt";
|
|
6858
|
+
return ext.replace(/^\./, "") || "docx";
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
async function convertStudioOfficeDocumentToMarkdown(resource: StudioLocalPreviewResource): Promise<{ text: string; label: string }> {
|
|
6862
|
+
if (resource.kind !== "office") throw new Error("This local resource is not a supported convertible document.");
|
|
6863
|
+
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
6864
|
+
const inputFormat = getStudioOfficePandocInputFormat(resource.extension);
|
|
6865
|
+
const result = await runStudioSubprocess(pandocCommand, [
|
|
6866
|
+
"-f", inputFormat,
|
|
6867
|
+
"-t", "markdown",
|
|
6868
|
+
"--wrap=none",
|
|
6869
|
+
resource.filePath,
|
|
6870
|
+
], {
|
|
6871
|
+
cwd: dirname(resource.filePath),
|
|
6872
|
+
timeoutMs: STUDIO_PANDOC_TIMEOUT_MS,
|
|
6873
|
+
stdoutMaxBytes: STUDIO_SUBPROCESS_OUTPUT_MAX_BYTES,
|
|
6874
|
+
label: "pandoc document conversion",
|
|
6875
|
+
notFoundMessage: "pandoc was not found. Install pandoc or set PANDOC_PATH to convert DOCX/ODT documents in Studio.",
|
|
6876
|
+
});
|
|
6877
|
+
if (result.code !== 0) {
|
|
6878
|
+
throw new Error(`pandoc failed with exit code ${result.code}${result.stderr ? `: ${result.stderr}` : ""}`);
|
|
6879
|
+
}
|
|
6880
|
+
if (result.stdoutTruncated) {
|
|
6881
|
+
throw new Error("Converted document exceeded Studio's import size limit.");
|
|
6882
|
+
}
|
|
6883
|
+
const label = `converted: ${resource.label || basename(resource.filePath) || "document"}`;
|
|
6884
|
+
const note = `<!-- ${label} from ${resource.filePath}. This is a Markdown conversion; saving will not update the original ${resource.extension || "document"} file. -->`;
|
|
6885
|
+
const body = result.stdout.trim();
|
|
6886
|
+
return { text: `${note}\n\n${body}\n`, label };
|
|
6887
|
+
}
|
|
6888
|
+
|
|
6889
|
+
async function respondLocalPreviewLinkJson(req: IncomingMessage, res: ServerResponse, requestUrl: URL, resource: StudioLocalPreviewResource, serverState: StudioServerState): Promise<void> {
|
|
6664
6890
|
const method = (req.method ?? "GET").toUpperCase();
|
|
6665
6891
|
if (method !== "GET" && method !== "HEAD") {
|
|
6666
6892
|
res.setHeader("Allow", "GET, HEAD");
|
|
@@ -6684,32 +6910,72 @@ function respondLocalPreviewLinkJson(req: IncomingMessage, res: ServerResponse,
|
|
|
6684
6910
|
return;
|
|
6685
6911
|
}
|
|
6686
6912
|
|
|
6913
|
+
if (action === "preview-url") {
|
|
6914
|
+
if (resource.kind !== "pdf" && resource.kind !== "image") {
|
|
6915
|
+
respondJson(res, 400, { ok: false, error: "This local resource cannot be opened in a Studio preview tab." });
|
|
6916
|
+
return;
|
|
6917
|
+
}
|
|
6918
|
+
const document = buildStudioLocalResourcePreviewDocument(resource);
|
|
6919
|
+
const docId = storeTransientStudioDocument(document);
|
|
6920
|
+
const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
|
|
6921
|
+
const parsedUrl = new URL(url);
|
|
6922
|
+
respondJson(res, 200, {
|
|
6923
|
+
...basePayload,
|
|
6924
|
+
url,
|
|
6925
|
+
relativeUrl: `${parsedUrl.pathname}${parsedUrl.search}`,
|
|
6926
|
+
});
|
|
6927
|
+
return;
|
|
6928
|
+
}
|
|
6929
|
+
|
|
6687
6930
|
if (action !== "document" && action !== "editor-url") {
|
|
6688
6931
|
respondJson(res, 400, { ok: false, error: "Unsupported local link action." });
|
|
6689
6932
|
return;
|
|
6690
6933
|
}
|
|
6691
|
-
if (resource.kind !== "text") {
|
|
6692
|
-
respondJson(res, 400, { ok: false, error: "This local resource is not a
|
|
6934
|
+
if (resource.kind !== "text" && resource.kind !== "office") {
|
|
6935
|
+
respondJson(res, 400, { ok: false, error: "This local resource is not a document Studio can load into the editor." });
|
|
6693
6936
|
return;
|
|
6694
6937
|
}
|
|
6695
6938
|
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6939
|
+
let document: InitialStudioDocument;
|
|
6940
|
+
let responseText = "";
|
|
6941
|
+
let converted = false;
|
|
6942
|
+
if (resource.kind === "office") {
|
|
6943
|
+
let conversion: { text: string; label: string };
|
|
6944
|
+
try {
|
|
6945
|
+
conversion = await convertStudioOfficeDocumentToMarkdown(resource);
|
|
6946
|
+
} catch (error) {
|
|
6947
|
+
respondJson(res, 400, { ok: false, error: `Document conversion failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
6948
|
+
return;
|
|
6949
|
+
}
|
|
6950
|
+
converted = true;
|
|
6951
|
+
responseText = conversion.text;
|
|
6952
|
+
document = {
|
|
6953
|
+
text: conversion.text,
|
|
6954
|
+
label: conversion.label,
|
|
6955
|
+
source: "blank",
|
|
6956
|
+
resourceDir: resource.resourceDir,
|
|
6957
|
+
};
|
|
6958
|
+
} else {
|
|
6959
|
+
const file = readStudioFile(resource.filePath, dirname(resource.filePath));
|
|
6960
|
+
if (file.ok === false) {
|
|
6961
|
+
respondJson(res, 400, { ok: false, error: file.message });
|
|
6962
|
+
return;
|
|
6963
|
+
}
|
|
6964
|
+
responseText = file.text;
|
|
6965
|
+
document = {
|
|
6966
|
+
text: file.text,
|
|
6967
|
+
label: resource.label || file.label,
|
|
6968
|
+
source: "file",
|
|
6969
|
+
path: file.resolvedPath,
|
|
6970
|
+
resourceDir: resource.resourceDir,
|
|
6971
|
+
};
|
|
6700
6972
|
}
|
|
6701
|
-
|
|
6702
|
-
const document: InitialStudioDocument = {
|
|
6703
|
-
text: file.text,
|
|
6704
|
-
label: resource.label || file.label,
|
|
6705
|
-
source: "file",
|
|
6706
|
-
path: file.resolvedPath,
|
|
6707
|
-
resourceDir: resource.resourceDir,
|
|
6708
|
-
};
|
|
6709
6973
|
if (action === "document") {
|
|
6710
6974
|
respondJson(res, 200, {
|
|
6711
6975
|
...basePayload,
|
|
6712
|
-
text:
|
|
6976
|
+
text: responseText,
|
|
6977
|
+
label: document.label,
|
|
6978
|
+
converted,
|
|
6713
6979
|
resourceDir: resource.resourceDir,
|
|
6714
6980
|
});
|
|
6715
6981
|
return;
|
|
@@ -6720,6 +6986,7 @@ function respondLocalPreviewLinkJson(req: IncomingMessage, res: ServerResponse,
|
|
|
6720
6986
|
const parsedUrl = new URL(url);
|
|
6721
6987
|
respondJson(res, 200, {
|
|
6722
6988
|
...basePayload,
|
|
6989
|
+
converted,
|
|
6723
6990
|
url,
|
|
6724
6991
|
relativeUrl: `${parsedUrl.pathname}${parsedUrl.search}`,
|
|
6725
6992
|
});
|
|
@@ -7876,10 +8143,15 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
7876
8143
|
};
|
|
7877
8144
|
}
|
|
7878
8145
|
|
|
7879
|
-
if (
|
|
8146
|
+
if (
|
|
8147
|
+
msg.type === "refresh_from_disk_request"
|
|
8148
|
+
&& typeof msg.requestId === "string"
|
|
8149
|
+
&& (msg.path === undefined || typeof msg.path === "string")
|
|
8150
|
+
) {
|
|
7880
8151
|
return {
|
|
7881
8152
|
type: "refresh_from_disk_request",
|
|
7882
8153
|
requestId: msg.requestId,
|
|
8154
|
+
path: typeof msg.path === "string" ? msg.path : undefined,
|
|
7883
8155
|
};
|
|
7884
8156
|
}
|
|
7885
8157
|
|
|
@@ -8303,15 +8575,17 @@ function createStudioTraceSnapshot(source: StudioTraceState): { traceState: Stud
|
|
|
8303
8575
|
};
|
|
8304
8576
|
}
|
|
8305
8577
|
const argsSummary = truncateStudioTraceSnapshotText(entry.argsSummary ?? "");
|
|
8578
|
+
const args = truncateStudioTraceSnapshotText(entry.args ?? entry.argsSummary ?? "");
|
|
8306
8579
|
const output = truncateStudioTraceSnapshotText(entry.output);
|
|
8307
8580
|
const snapshotImages = copyStudioTraceImagesForSnapshot(entry.images, imageBudget);
|
|
8308
|
-
truncated = truncated || argsSummary.truncated || output.truncated || snapshotImages.omitted > 0;
|
|
8581
|
+
truncated = truncated || argsSummary.truncated || args.truncated || output.truncated || snapshotImages.omitted > 0;
|
|
8309
8582
|
const omittedImageNote = snapshotImages.omitted > 0
|
|
8310
8583
|
? `[${snapshotImages.omitted} image preview${snapshotImages.omitted === 1 ? "" : "s"} omitted from saved Working view to keep history bounded.]`
|
|
8311
8584
|
: "";
|
|
8312
8585
|
return {
|
|
8313
8586
|
...entry,
|
|
8314
8587
|
argsSummary: argsSummary.text || null,
|
|
8588
|
+
args: args.text || null,
|
|
8315
8589
|
output: [output.text, omittedImageNote].filter(Boolean).join("\n"),
|
|
8316
8590
|
images: snapshotImages.images,
|
|
8317
8591
|
};
|
|
@@ -8522,6 +8796,34 @@ function summarizeStudioTraceToolArgs(toolName: string, args: unknown): string |
|
|
|
8522
8796
|
}
|
|
8523
8797
|
}
|
|
8524
8798
|
|
|
8799
|
+
function truncateStudioTraceToolArgs(text: string): string {
|
|
8800
|
+
const value = sanitizeStudioTraceOutputText(String(text || "").trim());
|
|
8801
|
+
if (!value || value.length <= STUDIO_TRACE_TOOL_ARGS_MAX_CHARS) return value;
|
|
8802
|
+
const keepHead = Math.max(1_000, Math.floor(STUDIO_TRACE_TOOL_ARGS_MAX_CHARS * 0.65));
|
|
8803
|
+
const keepTail = Math.max(1_000, STUDIO_TRACE_TOOL_ARGS_MAX_CHARS - keepHead - 160);
|
|
8804
|
+
const omitted = value.length - keepHead - keepTail;
|
|
8805
|
+
return `${value.slice(0, keepHead)}\n\n… ${omitted} chars omitted from tool input …\n\n${value.slice(value.length - keepTail)}`;
|
|
8806
|
+
}
|
|
8807
|
+
|
|
8808
|
+
function formatStudioTraceToolArgs(toolName: string, args: unknown): string | null {
|
|
8809
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
8810
|
+
const payload = (args && typeof args === "object") ? (args as Record<string, unknown>) : {};
|
|
8811
|
+
let raw = "";
|
|
8812
|
+
if (normalizedTool === "bash" && typeof payload.command === "string") {
|
|
8813
|
+
raw = payload.command;
|
|
8814
|
+
} else if ((normalizedTool === "repl_send" || normalizedTool === "studio_repl_send") && typeof payload.code === "string") {
|
|
8815
|
+
raw = payload.code;
|
|
8816
|
+
} else {
|
|
8817
|
+
try {
|
|
8818
|
+
raw = JSON.stringify(args, null, 2);
|
|
8819
|
+
} catch {
|
|
8820
|
+
raw = String(args ?? "");
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
const truncated = truncateStudioTraceToolArgs(raw);
|
|
8824
|
+
return truncated ? truncated : null;
|
|
8825
|
+
}
|
|
8826
|
+
|
|
8525
8827
|
function isStudioReplRuntime(value: unknown): value is StudioReplRuntime {
|
|
8526
8828
|
return value === "shell"
|
|
8527
8829
|
|| value === "python"
|
|
@@ -9563,7 +9865,7 @@ ${cssVarsBlock}
|
|
|
9563
9865
|
<button id="saveOverBtn" type="button" title="Overwrite current file with editor content. Shortcut: Cmd/Ctrl+S.">Save editor</button>
|
|
9564
9866
|
<button id="refreshFromDiskBtn" type="button" title="Reload the current file-backed document from disk.">Refresh from disk</button>
|
|
9565
9867
|
<button id="clearWorkspaceBtn" type="button" title="Clear editor text and reset this tab to a fresh blank draft. Saved files and responses are not changed.">Reset editor</button>
|
|
9566
|
-
<label class="file-label" title="
|
|
9868
|
+
<label class="file-label" title="Import a browser-selected text file into the editor as an unsaved copy. It will not be refreshable from disk until you save it.">Import file copy…<input id="fileInput" type="file" accept=".md,.markdown,.mdx,.qmd,.js,.mjs,.cjs,.jsx,.ts,.mts,.cts,.tsx,.py,.pyw,.sh,.bash,.zsh,.json,.jsonc,.json5,.rs,.c,.h,.cpp,.cxx,.cc,.hpp,.hxx,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.tex,.latex,.diff,.patch,.java,.go,.rb,.swift,.html,.htm,.css,.xml,.yaml,.yml,.toml,.lua,.txt,.rst,.adoc" /></label>
|
|
9567
9869
|
<button id="loadGitDiffBtn" type="button" title="Load the current git diff from the Studio context into the editor.">Load git diff</button>
|
|
9568
9870
|
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
9569
9871
|
<button id="zenModeBtn" class="zen-mode-btn" type="button" title="Hide secondary Studio controls. Shortcut: F9.">Zen</button>
|
|
@@ -9644,6 +9946,7 @@ ${cssVarsBlock}
|
|
|
9644
9946
|
<option value="c">Syntax highlight: C</option>
|
|
9645
9947
|
<option value="cpp">Syntax highlight: C++</option>
|
|
9646
9948
|
<option value="css">Syntax highlight: CSS</option>
|
|
9949
|
+
<option value="csv">Syntax highlight: CSV</option>
|
|
9647
9950
|
<option value="diff">Syntax highlight: Diff</option>
|
|
9648
9951
|
<option value="fortran">Syntax highlight: Fortran</option>
|
|
9649
9952
|
<option value="go">Syntax highlight: Go</option>
|
|
@@ -9662,6 +9965,7 @@ ${cssVarsBlock}
|
|
|
9662
9965
|
<option value="rust">Syntax highlight: Rust</option>
|
|
9663
9966
|
<option value="swift">Syntax highlight: Swift</option>
|
|
9664
9967
|
<option value="toml">Syntax highlight: TOML</option>
|
|
9968
|
+
<option value="tsv">Syntax highlight: TSV</option>
|
|
9665
9969
|
<option value="typescript">Syntax highlight: TypeScript</option>
|
|
9666
9970
|
<option value="xml">Syntax highlight: XML</option>
|
|
9667
9971
|
<option value="yaml">Syntax highlight: YAML</option>
|
|
@@ -9847,11 +10151,11 @@ ${cssVarsBlock}
|
|
|
9847
10151
|
<div class="shortcuts-header">
|
|
9848
10152
|
<div>
|
|
9849
10153
|
<h2 id="shortcutsTitle">Keyboard shortcuts</h2>
|
|
9850
|
-
<p class="shortcuts-description">Studio navigation and high-frequency actions.</p>
|
|
10154
|
+
<p class="shortcuts-description">Studio navigation and high-frequency actions. Use arrow keys, Page Up/Down, Home/End, or mouse/trackpad to scroll.</p>
|
|
9851
10155
|
</div>
|
|
9852
10156
|
<button id="shortcutsCloseBtn" class="shortcuts-close-btn" type="button" aria-label="Close keyboard shortcuts">Close</button>
|
|
9853
10157
|
</div>
|
|
9854
|
-
<div class="shortcuts-body">
|
|
10158
|
+
<div id="shortcutsBody" class="shortcuts-body" tabindex="0" aria-label="Keyboard shortcuts list">
|
|
9855
10159
|
<section class="shortcuts-group">
|
|
9856
10160
|
<h3>Navigation</h3>
|
|
9857
10161
|
<dl>
|
|
@@ -10774,7 +11078,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
10774
11078
|
const existingId = studioTraceToolEntryIds.get(toolCallId);
|
|
10775
11079
|
if (existingId) {
|
|
10776
11080
|
const existing = studioTraceState.entries.find((entry) => entry.id === existingId);
|
|
10777
|
-
if (existing && existing.type === "tool")
|
|
11081
|
+
if (existing && existing.type === "tool") {
|
|
11082
|
+
if (args !== undefined) {
|
|
11083
|
+
existing.toolName = toolName;
|
|
11084
|
+
existing.label = deriveToolActivityLabel(toolName, args);
|
|
11085
|
+
existing.argsSummary = summarizeStudioTraceToolArgs(toolName, args);
|
|
11086
|
+
existing.args = formatStudioTraceToolArgs(toolName, args);
|
|
11087
|
+
existing.updatedAt = Date.now();
|
|
11088
|
+
upsertStudioTraceEntry(existing);
|
|
11089
|
+
}
|
|
11090
|
+
return existing;
|
|
11091
|
+
}
|
|
10778
11092
|
}
|
|
10779
11093
|
if (studioTraceState.runId == null || studioTraceState.status === "idle") {
|
|
10780
11094
|
resetStudioTraceForRun();
|
|
@@ -10787,6 +11101,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
10787
11101
|
toolName,
|
|
10788
11102
|
label: deriveToolActivityLabel(toolName, args),
|
|
10789
11103
|
argsSummary: summarizeStudioTraceToolArgs(toolName, args),
|
|
11104
|
+
args: formatStudioTraceToolArgs(toolName, args),
|
|
10790
11105
|
output: "",
|
|
10791
11106
|
images: [],
|
|
10792
11107
|
startedAt: now,
|
|
@@ -10809,6 +11124,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
10809
11124
|
images?: StudioTraceImage[],
|
|
10810
11125
|
) => {
|
|
10811
11126
|
const entry = ensureStudioTraceToolEntry(toolCallId, toolName, args);
|
|
11127
|
+
if (!entry.argsSummary) entry.argsSummary = summarizeStudioTraceToolArgs(toolName, args);
|
|
11128
|
+
if (!entry.args) entry.args = formatStudioTraceToolArgs(toolName, args);
|
|
10812
11129
|
entry.output = output;
|
|
10813
11130
|
if (Array.isArray(images)) entry.images = images;
|
|
10814
11131
|
entry.status = status;
|
|
@@ -11959,16 +12276,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
11959
12276
|
sendToClient(client, { type: "busy", requestId: msg.requestId, message: "Studio is busy." });
|
|
11960
12277
|
return;
|
|
11961
12278
|
}
|
|
11962
|
-
|
|
12279
|
+
const requestedPath = typeof msg.path === "string" && msg.path.trim() ? msg.path.trim() : "";
|
|
12280
|
+
const refreshPath = requestedPath || initialStudioDocument?.path || "";
|
|
12281
|
+
if (!refreshPath) {
|
|
11963
12282
|
sendToClient(client, {
|
|
11964
12283
|
type: "error",
|
|
11965
12284
|
requestId: msg.requestId,
|
|
11966
|
-
message: "Refresh from disk
|
|
12285
|
+
message: "Refresh from disk needs a file path. Use Files → Open here, Files → Open file tab, or /studio-editor-only <path> for a refreshable editor tab.",
|
|
11967
12286
|
});
|
|
11968
12287
|
return;
|
|
11969
12288
|
}
|
|
11970
12289
|
|
|
11971
|
-
const refreshed = readStudioFile(
|
|
12290
|
+
const refreshed = readStudioFile(refreshPath, studioCwd);
|
|
11972
12291
|
if (refreshed.ok === false) {
|
|
11973
12292
|
sendToClient(client, {
|
|
11974
12293
|
type: "error",
|
|
@@ -11978,18 +12297,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
11978
12297
|
return;
|
|
11979
12298
|
}
|
|
11980
12299
|
|
|
11981
|
-
|
|
12300
|
+
const refreshedDocument: InitialStudioDocument = {
|
|
11982
12301
|
text: refreshed.text,
|
|
11983
12302
|
label: refreshed.label,
|
|
11984
12303
|
source: "file",
|
|
11985
12304
|
path: refreshed.resolvedPath,
|
|
11986
12305
|
resourceDir: dirname(refreshed.resolvedPath),
|
|
11987
12306
|
};
|
|
12307
|
+
if (!requestedPath || initialStudioDocument?.path === refreshed.resolvedPath) {
|
|
12308
|
+
initialStudioDocument = refreshedDocument;
|
|
12309
|
+
}
|
|
11988
12310
|
|
|
11989
|
-
|
|
12311
|
+
sendToClient(client, {
|
|
11990
12312
|
type: "studio_document",
|
|
11991
12313
|
requestId: msg.requestId,
|
|
11992
|
-
document:
|
|
12314
|
+
document: refreshedDocument,
|
|
11993
12315
|
message: `Reloaded ${refreshed.label} from disk.`,
|
|
11994
12316
|
});
|
|
11995
12317
|
return;
|
|
@@ -12073,6 +12395,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12073
12395
|
};
|
|
12074
12396
|
|
|
12075
12397
|
const disposePreparedPdfExport = (entry: PreparedStudioPdfExport | null | undefined) => {
|
|
12398
|
+
if (entry?.persistent) return;
|
|
12076
12399
|
if (!entry?.tempDirPath) return;
|
|
12077
12400
|
void rm(entry.tempDirPath, { recursive: true, force: true }).catch(() => undefined);
|
|
12078
12401
|
};
|
|
@@ -12101,7 +12424,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12101
12424
|
}
|
|
12102
12425
|
};
|
|
12103
12426
|
|
|
12104
|
-
const storePreparedPdfExport = (pdf: Buffer, filename: string, warning?: string): string => {
|
|
12427
|
+
const storePreparedPdfExport = (pdf: Buffer, filename: string, warning?: string, filePath?: string): string => {
|
|
12105
12428
|
prunePreparedPdfExports();
|
|
12106
12429
|
const exportId = randomUUID();
|
|
12107
12430
|
preparedPdfExports.set(exportId, {
|
|
@@ -12109,6 +12432,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12109
12432
|
filename,
|
|
12110
12433
|
warning,
|
|
12111
12434
|
createdAt: Date.now(),
|
|
12435
|
+
filePath,
|
|
12436
|
+
persistent: Boolean(filePath),
|
|
12112
12437
|
});
|
|
12113
12438
|
return exportId;
|
|
12114
12439
|
};
|
|
@@ -12117,7 +12442,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12117
12442
|
prunePreparedPdfExports();
|
|
12118
12443
|
const entry = preparedPdfExports.get(exportId);
|
|
12119
12444
|
if (!entry) return null;
|
|
12120
|
-
if (entry.filePath && entry.tempDirPath) return entry;
|
|
12445
|
+
if (entry.filePath && (entry.tempDirPath || entry.persistent)) return entry;
|
|
12121
12446
|
|
|
12122
12447
|
const tempDirPath = join(tmpdir(), `pi-studio-prepared-pdf-${Date.now()}-${randomUUID()}`);
|
|
12123
12448
|
const filePath = join(tempDirPath, sanitizePdfFilename(entry.filename));
|
|
@@ -12167,6 +12492,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12167
12492
|
};
|
|
12168
12493
|
|
|
12169
12494
|
const disposePreparedHtmlExport = (entry: PreparedStudioHtmlExport | null | undefined) => {
|
|
12495
|
+
if (entry?.persistent) return;
|
|
12170
12496
|
if (!entry?.tempDirPath) return;
|
|
12171
12497
|
void rm(entry.tempDirPath, { recursive: true, force: true }).catch(() => undefined);
|
|
12172
12498
|
};
|
|
@@ -12195,7 +12521,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12195
12521
|
}
|
|
12196
12522
|
};
|
|
12197
12523
|
|
|
12198
|
-
const storePreparedHtmlExport = (html: Buffer, filename: string, warning?: string): string => {
|
|
12524
|
+
const storePreparedHtmlExport = (html: Buffer, filename: string, warning?: string, filePath?: string): string => {
|
|
12199
12525
|
prunePreparedHtmlExports();
|
|
12200
12526
|
const exportId = randomUUID();
|
|
12201
12527
|
preparedHtmlExports.set(exportId, {
|
|
@@ -12203,6 +12529,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12203
12529
|
filename,
|
|
12204
12530
|
warning,
|
|
12205
12531
|
createdAt: Date.now(),
|
|
12532
|
+
filePath,
|
|
12533
|
+
persistent: Boolean(filePath),
|
|
12206
12534
|
});
|
|
12207
12535
|
return exportId;
|
|
12208
12536
|
};
|
|
@@ -12211,7 +12539,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12211
12539
|
prunePreparedHtmlExports();
|
|
12212
12540
|
const entry = preparedHtmlExports.get(exportId);
|
|
12213
12541
|
if (!entry) return null;
|
|
12214
|
-
if (entry.filePath && entry.tempDirPath) return entry;
|
|
12542
|
+
if (entry.filePath && (entry.tempDirPath || entry.persistent)) return entry;
|
|
12215
12543
|
|
|
12216
12544
|
const tempDirPath = join(tmpdir(), `pi-studio-prepared-html-${Date.now()}-${randomUUID()}`);
|
|
12217
12545
|
const filePath = join(tempDirPath, sanitizeHtmlFilename(entry.filename));
|
|
@@ -12632,7 +12960,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12632
12960
|
|
|
12633
12961
|
try {
|
|
12634
12962
|
const { pdf, warning } = await renderStudioPdfWithPandoc(markdown, isLatex, resourcePath, editorPdfLanguage, sourcePath || undefined);
|
|
12635
|
-
const
|
|
12963
|
+
const writeResult = writeStudioPreviewExportFile(buildStudioPreviewExportPath(sourcePath || undefined, userResourceDir || undefined, studioCwd, filename), pdf);
|
|
12964
|
+
const exportId = storePreparedPdfExport(pdf, filename, warning, writeResult.filePath ?? undefined);
|
|
12636
12965
|
const token = serverState?.token ?? "";
|
|
12637
12966
|
let openedExternal = false;
|
|
12638
12967
|
let openError: string | null = null;
|
|
@@ -12649,6 +12978,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12649
12978
|
respondJson(res, 200, {
|
|
12650
12979
|
ok: true,
|
|
12651
12980
|
filename,
|
|
12981
|
+
path: writeResult.filePath,
|
|
12982
|
+
writeError: writeResult.error,
|
|
12652
12983
|
warning: warning ?? null,
|
|
12653
12984
|
openedExternal,
|
|
12654
12985
|
openError,
|
|
@@ -12743,7 +13074,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12743
13074
|
themeVars,
|
|
12744
13075
|
},
|
|
12745
13076
|
);
|
|
12746
|
-
const
|
|
13077
|
+
const writeResult = writeStudioPreviewExportFile(buildStudioPreviewExportPath(sourcePath || undefined, userResourceDir || undefined, studioCwd, filename), html);
|
|
13078
|
+
const exportId = storePreparedHtmlExport(html, filename, warning, writeResult.filePath ?? undefined);
|
|
12747
13079
|
const token = serverState?.token ?? "";
|
|
12748
13080
|
let openedExternal = false;
|
|
12749
13081
|
let openError: string | null = null;
|
|
@@ -12760,6 +13092,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
12760
13092
|
respondJson(res, 200, {
|
|
12761
13093
|
ok: true,
|
|
12762
13094
|
filename,
|
|
13095
|
+
path: writeResult.filePath,
|
|
13096
|
+
writeError: writeResult.error,
|
|
12763
13097
|
warning: warning ?? null,
|
|
12764
13098
|
openedExternal,
|
|
12765
13099
|
openError,
|
|
@@ -13052,17 +13386,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
13052
13386
|
return;
|
|
13053
13387
|
}
|
|
13054
13388
|
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13389
|
+
void (async () => {
|
|
13390
|
+
try {
|
|
13391
|
+
const resource = resolveStudioLocalPreviewResourcePath(
|
|
13392
|
+
requestUrl.searchParams.get("path") ?? "",
|
|
13393
|
+
requestUrl.searchParams.get("sourcePath") ?? undefined,
|
|
13394
|
+
requestUrl.searchParams.get("resourceDir") ?? undefined,
|
|
13395
|
+
studioCwd,
|
|
13396
|
+
);
|
|
13397
|
+
await respondLocalPreviewLinkJson(req, res, requestUrl, resource, serverState);
|
|
13398
|
+
} catch (error) {
|
|
13399
|
+
respondJson(res, 404, { ok: false, error: `Local resource unavailable: ${error instanceof Error ? error.message : String(error)}` });
|
|
13400
|
+
}
|
|
13401
|
+
})();
|
|
13066
13402
|
return;
|
|
13067
13403
|
}
|
|
13068
13404
|
|
|
@@ -13386,7 +13722,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
13386
13722
|
if (!agentBusy) return;
|
|
13387
13723
|
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
13388
13724
|
const input = (event as { input?: unknown }).input;
|
|
13725
|
+
const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : "";
|
|
13389
13726
|
const label = deriveToolActivityLabel(toolName, input);
|
|
13727
|
+
if (toolCallId) ensureStudioTraceToolEntry(toolCallId, toolName, input);
|
|
13390
13728
|
emitDebugEvent("tool_call", { toolName, label, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
13391
13729
|
setTerminalActivity("tool", toolName, label);
|
|
13392
13730
|
});
|