pi-studio 0.5.5 → 0.5.7
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 +15 -1
- package/README.md +2 -1
- package/WORKFLOW.md +2 -2
- package/index.ts +400 -48
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `pi-studio` are documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.5.7] — 2026-03-12
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Preview rendering now passes `--wrap=none` to pandoc and preview-side annotation matching now tolerates embedded newlines, fixing missed `[an: ...]` highlights in preview for longer annotations.
|
|
11
|
+
- Editor sync indicator is now intentionally quiet: Studio only shows the badge when the editor exactly matches the current response/thinking, and hides it while drafting/out-of-sync.
|
|
12
|
+
- Response history navigation now includes **Last response ▶|** for jumping straight back to the newest loaded history item.
|
|
13
|
+
- Renamed **Get latest response** to **Fetch latest response** for clearer distinction from history navigation, and moved **Load response into editor** ahead of **Load response prompt into editor** in the action row.
|
|
14
|
+
|
|
5
15
|
## [0.4.3] — 2026-03-04
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -86,7 +96,11 @@ All notable changes to `pi-studio` are documented here.
|
|
|
86
96
|
- Active pane indicator simplified to subtle border color change (removed thick top accent bar).
|
|
87
97
|
- Panel shadows, button hierarchy (filled accent for primary actions), heading scale, blockquote/table styling improvements.
|
|
88
98
|
|
|
89
|
-
## [
|
|
99
|
+
## [0.5.6] — 2026-03-10
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
- Studio monospace surfaces now use a shared `--font-mono` stack, with best-effort terminal-font detection (Ghostty/WezTerm/Kitty/Alacritty config when available) and `PI_STUDIO_FONT_MONO` as a manual override.
|
|
103
|
+
- In-flight **Run editor text** / **Critique editor text** requests now swap the triggering button into an in-place theme-aware **Stop** state while disabling the other action.
|
|
90
104
|
|
|
91
105
|
## [0.5.5] — 2026-03-09
|
|
92
106
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
16
16
|
|
|
17
17
|
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Thinking/Editor Preview** (right)
|
|
18
18
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
19
|
-
- Browses response history (`Prev/Next`) and loads either:
|
|
19
|
+
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
20
20
|
- response text
|
|
21
21
|
- critique notes/full critique
|
|
22
22
|
- assistant thinking (when available)
|
|
@@ -62,6 +62,7 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
62
62
|
|
|
63
63
|
- Local-only server (`127.0.0.1`) with rotating tokenized URLs.
|
|
64
64
|
- Studio is designed as a complement to terminal pi, not a replacement.
|
|
65
|
+
- Editor/code font uses a best-effort terminal-monospace match when the current terminal config exposes it; set `PI_STUDIO_FONT_MONO` to force a specific CSS `font-family` stack.
|
|
65
66
|
- Full preview/PDF quality depends on `pandoc` (and `xelatex` for PDF):
|
|
66
67
|
- `brew install pandoc`
|
|
67
68
|
- install TeX Live/MacTeX for PDF export
|
package/WORKFLOW.md
CHANGED
|
@@ -79,10 +79,10 @@ Rules:
|
|
|
79
79
|
- Header view toggles: `Left: Editor (Raw|Preview)`, `Right: Response (Raw|Preview) | Editor (Preview)`
|
|
80
80
|
- Preview mode uses server-side `pandoc` rendering (math-aware) with plain-markdown fallback when renderer is unavailable.
|
|
81
81
|
- Editor actions: **Insert/Remove annotated reply header**, **Annotations: On|Hidden**, **Strip annotations…**, **Run editor text**, **Critique editor text** (+ critique focus), **Send to pi editor**, **Copy editor text**, **Save .annotated.md**
|
|
82
|
-
- Response actions include `Auto-update response: On|Off`, **
|
|
82
|
+
- Response actions include `Auto-update response: On|Off`, **Fetch latest response**, response-history browse (`Prev/Next/Last`), **Load response into editor**, and **Load response prompt into editor**
|
|
83
83
|
- Source badge: `blank | last model response | file <path> | upload`
|
|
84
84
|
- Response badge: `none | assistant response | assistant critique` (+ timestamp)
|
|
85
|
-
- Sync badge:
|
|
85
|
+
- Sync badge: shown only when the editor exactly matches the currently viewed response/thinking (`In sync with response | In sync with thinking`)
|
|
86
86
|
- Footer WS/status phases: `Connecting`, `Ready`, `Submitting`, `Disconnected`
|
|
87
87
|
|
|
88
88
|
---
|
package/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { randomUUID } from "node:crypto";
|
|
|
4
4
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
7
|
+
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import { URL } from "node:url";
|
|
10
10
|
import { WebSocketServer, WebSocket, type RawData } from "ws";
|
|
@@ -120,6 +120,11 @@ interface GetFromEditorRequestMessage {
|
|
|
120
120
|
requestId: string;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
interface CancelRequestMessage {
|
|
124
|
+
type: "cancel_request";
|
|
125
|
+
requestId: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
123
128
|
type IncomingStudioMessage =
|
|
124
129
|
| HelloMessage
|
|
125
130
|
| PingMessage
|
|
@@ -131,7 +136,8 @@ type IncomingStudioMessage =
|
|
|
131
136
|
| SaveAsRequestMessage
|
|
132
137
|
| SaveOverRequestMessage
|
|
133
138
|
| SendToEditorRequestMessage
|
|
134
|
-
| GetFromEditorRequestMessage
|
|
139
|
+
| GetFromEditorRequestMessage
|
|
140
|
+
| CancelRequestMessage;
|
|
135
141
|
|
|
136
142
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
137
143
|
const PREVIEW_RENDER_MAX_CHARS = 400_000;
|
|
@@ -453,6 +459,205 @@ interface ThemeExportPalette {
|
|
|
453
459
|
|
|
454
460
|
const themeExportPaletteCache = new Map<string, ThemeExportPalette | null>();
|
|
455
461
|
|
|
462
|
+
const DEFAULT_MONO_FONT_FAMILIES = [
|
|
463
|
+
"ui-monospace",
|
|
464
|
+
"SFMono-Regular",
|
|
465
|
+
"Menlo",
|
|
466
|
+
"Monaco",
|
|
467
|
+
"Consolas",
|
|
468
|
+
"Liberation Mono",
|
|
469
|
+
"Courier New",
|
|
470
|
+
"monospace",
|
|
471
|
+
] as const;
|
|
472
|
+
|
|
473
|
+
const CSS_GENERIC_FONT_FAMILIES = new Set([
|
|
474
|
+
"serif",
|
|
475
|
+
"sans-serif",
|
|
476
|
+
"monospace",
|
|
477
|
+
"cursive",
|
|
478
|
+
"fantasy",
|
|
479
|
+
"system-ui",
|
|
480
|
+
"emoji",
|
|
481
|
+
"math",
|
|
482
|
+
"fangsong",
|
|
483
|
+
"ui-serif",
|
|
484
|
+
"ui-sans-serif",
|
|
485
|
+
"ui-monospace",
|
|
486
|
+
"ui-rounded",
|
|
487
|
+
]);
|
|
488
|
+
|
|
489
|
+
let cachedStudioMonoFontStack: string | null = null;
|
|
490
|
+
|
|
491
|
+
function getHomeDirectory(): string {
|
|
492
|
+
return process.env.HOME ?? homedir();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getXdgConfigDirectory(): string {
|
|
496
|
+
const configured = process.env.XDG_CONFIG_HOME?.trim();
|
|
497
|
+
if (configured) return configured;
|
|
498
|
+
return join(getHomeDirectory(), ".config");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function sanitizeCssValue(value: string): string {
|
|
502
|
+
return value.replace(/[\r\n;]+/g, " ").trim();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function stripSimpleInlineComment(value: string): string {
|
|
506
|
+
let quote: '"' | "'" | null = null;
|
|
507
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
508
|
+
const char = value[i];
|
|
509
|
+
if (quote) {
|
|
510
|
+
if (char === quote && value[i - 1] !== "\\") quote = null;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (char === '"' || char === "'") {
|
|
514
|
+
quote = char;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (char === "#") {
|
|
518
|
+
return value.slice(0, i).trim();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return value.trim();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function normalizeConfiguredFontFamily(value: string | undefined): string | undefined {
|
|
525
|
+
if (!value) return undefined;
|
|
526
|
+
const sanitized = sanitizeCssValue(stripSimpleInlineComment(value));
|
|
527
|
+
if (!sanitized) return undefined;
|
|
528
|
+
const unquoted =
|
|
529
|
+
(sanitized.startsWith('"') && sanitized.endsWith('"'))
|
|
530
|
+
|| (sanitized.startsWith("'") && sanitized.endsWith("'"))
|
|
531
|
+
? sanitized.slice(1, -1).trim()
|
|
532
|
+
: sanitized;
|
|
533
|
+
return unquoted || undefined;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function formatCssFontFamilyToken(value: string): string {
|
|
537
|
+
const trimmed = sanitizeCssValue(value);
|
|
538
|
+
if (!trimmed) return "";
|
|
539
|
+
if (CSS_GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase())) return trimmed;
|
|
540
|
+
if (
|
|
541
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"'))
|
|
542
|
+
|| (trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
543
|
+
) {
|
|
544
|
+
return trimmed;
|
|
545
|
+
}
|
|
546
|
+
return `"${trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function readFirstExistingTextFile(paths: string[]): string | undefined {
|
|
550
|
+
for (const path of paths) {
|
|
551
|
+
try {
|
|
552
|
+
const text = readFileSync(path, "utf-8");
|
|
553
|
+
if (text.trim()) return text;
|
|
554
|
+
} catch {
|
|
555
|
+
// Ignore missing/unreadable files
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function detectGhosttyFontFamily(): string | undefined {
|
|
562
|
+
const home = getHomeDirectory();
|
|
563
|
+
const content = readFirstExistingTextFile([
|
|
564
|
+
join(getXdgConfigDirectory(), "ghostty", "config"),
|
|
565
|
+
join(home, "Library", "Application Support", "com.mitchellh.ghostty", "config"),
|
|
566
|
+
]);
|
|
567
|
+
if (!content) return undefined;
|
|
568
|
+
const match = content.match(/^\s*font-family\s*=\s*(.+?)\s*$/m);
|
|
569
|
+
return normalizeConfiguredFontFamily(match?.[1]);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function detectKittyFontFamily(): string | undefined {
|
|
573
|
+
const content = readFirstExistingTextFile([
|
|
574
|
+
join(getXdgConfigDirectory(), "kitty", "kitty.conf"),
|
|
575
|
+
]);
|
|
576
|
+
if (!content) return undefined;
|
|
577
|
+
const match = content.match(/^\s*font_family\s+(.+?)\s*$/m);
|
|
578
|
+
return normalizeConfiguredFontFamily(match?.[1]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function detectWezTermFontFamily(): string | undefined {
|
|
582
|
+
const home = getHomeDirectory();
|
|
583
|
+
const content = readFirstExistingTextFile([
|
|
584
|
+
join(getXdgConfigDirectory(), "wezterm", "wezterm.lua"),
|
|
585
|
+
join(home, ".wezterm.lua"),
|
|
586
|
+
]);
|
|
587
|
+
if (!content) return undefined;
|
|
588
|
+
const patterns = [
|
|
589
|
+
/font_with_fallback\s*\(\s*\{[\s\S]*?["']([^"']+)["']/m,
|
|
590
|
+
/font\s*\(\s*["']([^"']+)["']/m,
|
|
591
|
+
/font\s*=\s*["']([^"']+)["']/m,
|
|
592
|
+
/family\s*=\s*["']([^"']+)["']/m,
|
|
593
|
+
];
|
|
594
|
+
for (const pattern of patterns) {
|
|
595
|
+
const family = normalizeConfiguredFontFamily(content.match(pattern)?.[1]);
|
|
596
|
+
if (family) return family;
|
|
597
|
+
}
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function detectAlacrittyFontFamily(): string | undefined {
|
|
602
|
+
const content = readFirstExistingTextFile([
|
|
603
|
+
join(getXdgConfigDirectory(), "alacritty", "alacritty.toml"),
|
|
604
|
+
join(getXdgConfigDirectory(), "alacritty.toml"),
|
|
605
|
+
join(getXdgConfigDirectory(), "alacritty", "alacritty.yml"),
|
|
606
|
+
join(getXdgConfigDirectory(), "alacritty", "alacritty.yaml"),
|
|
607
|
+
]);
|
|
608
|
+
if (!content) return undefined;
|
|
609
|
+
const patterns = [
|
|
610
|
+
/^\s*family\s*=\s*["']([^"']+)["']\s*$/m,
|
|
611
|
+
/^\s*family\s*:\s*["']?([^"'#\n]+)["']?\s*$/m,
|
|
612
|
+
];
|
|
613
|
+
for (const pattern of patterns) {
|
|
614
|
+
const family = normalizeConfiguredFontFamily(content.match(pattern)?.[1]);
|
|
615
|
+
if (family) return family;
|
|
616
|
+
}
|
|
617
|
+
return undefined;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function detectTerminalMonospaceFontFamily(): string | undefined {
|
|
621
|
+
const termProgram = (process.env.TERM_PROGRAM ?? "").trim().toLowerCase();
|
|
622
|
+
const term = (process.env.TERM ?? "").trim().toLowerCase();
|
|
623
|
+
|
|
624
|
+
if (termProgram === "ghostty" || term.includes("ghostty")) return detectGhosttyFontFamily();
|
|
625
|
+
if (termProgram === "wezterm") return detectWezTermFontFamily();
|
|
626
|
+
if (termProgram === "kitty" || term.includes("kitty")) return detectKittyFontFamily();
|
|
627
|
+
if (termProgram === "alacritty") return detectAlacrittyFontFamily();
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function buildMonoFontStack(primaryFamily?: string): string {
|
|
632
|
+
const entries: string[] = [];
|
|
633
|
+
const seen = new Set<string>();
|
|
634
|
+
const push = (family: string) => {
|
|
635
|
+
const trimmed = family.trim();
|
|
636
|
+
if (!trimmed) return;
|
|
637
|
+
const key = trimmed.replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
638
|
+
if (seen.has(key)) return;
|
|
639
|
+
seen.add(key);
|
|
640
|
+
entries.push(formatCssFontFamilyToken(trimmed));
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
if (primaryFamily) push(primaryFamily);
|
|
644
|
+
for (const family of DEFAULT_MONO_FONT_FAMILIES) push(family);
|
|
645
|
+
return entries.join(", ");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function getStudioMonoFontStack(): string {
|
|
649
|
+
if (cachedStudioMonoFontStack) return cachedStudioMonoFontStack;
|
|
650
|
+
|
|
651
|
+
const override = sanitizeCssValue(process.env.PI_STUDIO_FONT_MONO ?? "");
|
|
652
|
+
if (override) {
|
|
653
|
+
cachedStudioMonoFontStack = override;
|
|
654
|
+
return cachedStudioMonoFontStack;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
cachedStudioMonoFontStack = buildMonoFontStack(detectTerminalMonospaceFontFamily());
|
|
658
|
+
return cachedStudioMonoFontStack;
|
|
659
|
+
}
|
|
660
|
+
|
|
456
661
|
function resolveThemeExportValue(
|
|
457
662
|
value: string | number | undefined,
|
|
458
663
|
vars: Record<string, string | number>,
|
|
@@ -865,7 +1070,7 @@ function normalizeObsidianImages(markdown: string): string {
|
|
|
865
1070
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
|
|
866
1071
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
867
1072
|
const inputFormat = isLatex ? "latex" : "gfm+tex_math_dollars-raw_html";
|
|
868
|
-
const args = ["-f", inputFormat, "-t", "html5", "--mathml"];
|
|
1073
|
+
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
|
|
869
1074
|
if (resourcePath) {
|
|
870
1075
|
args.push(`--resource-path=${resourcePath}`);
|
|
871
1076
|
// Embed images as data URIs so they render in the browser preview
|
|
@@ -1498,6 +1703,13 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
1498
1703
|
};
|
|
1499
1704
|
}
|
|
1500
1705
|
|
|
1706
|
+
if (msg.type === "cancel_request" && typeof msg.requestId === "string") {
|
|
1707
|
+
return {
|
|
1708
|
+
type: "cancel_request",
|
|
1709
|
+
requestId: msg.requestId,
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1501
1713
|
return null;
|
|
1502
1714
|
}
|
|
1503
1715
|
|
|
@@ -1693,6 +1905,7 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
1693
1905
|
? "0 1px 2px rgba(15, 23, 42, 0.03), 0 4px 14px rgba(15, 23, 42, 0.04)"
|
|
1694
1906
|
: "0 1px 2px rgba(0, 0, 0, 0.36), 0 6px 18px rgba(0, 0, 0, 0.22)";
|
|
1695
1907
|
const accentContrast = style.mode === "light" ? "#ffffff" : "#0e1616";
|
|
1908
|
+
const errorContrast = style.mode === "light" ? "#ffffff" : "#0e1616";
|
|
1696
1909
|
const blockquoteBg = withAlpha(
|
|
1697
1910
|
style.palette.mdQuoteBorder,
|
|
1698
1911
|
style.mode === "light" ? 0.10 : 0.16,
|
|
@@ -1706,6 +1919,7 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
1706
1919
|
const editorBg = style.mode === "light"
|
|
1707
1920
|
? blendColors(style.palette.panel, "#ffffff", 0.5)
|
|
1708
1921
|
: style.palette.panel;
|
|
1922
|
+
const monoFontStack = getStudioMonoFontStack();
|
|
1709
1923
|
|
|
1710
1924
|
return {
|
|
1711
1925
|
"color-scheme": style.mode,
|
|
@@ -1747,9 +1961,11 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
1747
1961
|
"--syntax-punctuation": style.palette.syntaxPunctuation,
|
|
1748
1962
|
"--panel-shadow": panelShadow,
|
|
1749
1963
|
"--accent-contrast": accentContrast,
|
|
1964
|
+
"--error-contrast": errorContrast,
|
|
1750
1965
|
"--blockquote-bg": blockquoteBg,
|
|
1751
1966
|
"--table-alt-bg": tableAltBg,
|
|
1752
1967
|
"--editor-bg": editorBg,
|
|
1968
|
+
"--font-mono": monoFontStack,
|
|
1753
1969
|
};
|
|
1754
1970
|
}
|
|
1755
1971
|
|
|
@@ -1780,10 +1996,11 @@ function buildStudioHtml(
|
|
|
1780
1996
|
: "";
|
|
1781
1997
|
const style = getStudioThemeStyle(theme);
|
|
1782
1998
|
const vars = buildThemeCssVars(style);
|
|
1999
|
+
const monoFontStack = vars["--font-mono"] ?? buildMonoFontStack();
|
|
1783
2000
|
const mermaidConfig = {
|
|
1784
2001
|
startOnLoad: false,
|
|
1785
2002
|
theme: "base",
|
|
1786
|
-
fontFamily:
|
|
2003
|
+
fontFamily: monoFontStack,
|
|
1787
2004
|
flowchart: {
|
|
1788
2005
|
curve: "basis",
|
|
1789
2006
|
},
|
|
@@ -1909,6 +2126,14 @@ ${cssVarsBlock}
|
|
|
1909
2126
|
}
|
|
1910
2127
|
|
|
1911
2128
|
#sendRunBtn,
|
|
2129
|
+
#critiqueBtn {
|
|
2130
|
+
min-width: 10rem;
|
|
2131
|
+
display: inline-flex;
|
|
2132
|
+
justify-content: center;
|
|
2133
|
+
align-items: center;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
#sendRunBtn:not(:disabled):not(.request-stop-active),
|
|
1912
2137
|
#loadResponseBtn:not(:disabled):not([hidden]) {
|
|
1913
2138
|
background: var(--accent);
|
|
1914
2139
|
border-color: var(--accent);
|
|
@@ -1916,11 +2141,24 @@ ${cssVarsBlock}
|
|
|
1916
2141
|
font-weight: 600;
|
|
1917
2142
|
}
|
|
1918
2143
|
|
|
1919
|
-
#sendRunBtn:not(:disabled):hover,
|
|
2144
|
+
#sendRunBtn:not(:disabled):not(.request-stop-active):hover,
|
|
1920
2145
|
#loadResponseBtn:not(:disabled):not([hidden]):hover {
|
|
1921
2146
|
filter: brightness(0.95);
|
|
1922
2147
|
}
|
|
1923
2148
|
|
|
2149
|
+
#sendRunBtn.request-stop-active,
|
|
2150
|
+
#critiqueBtn.request-stop-active {
|
|
2151
|
+
background: var(--error);
|
|
2152
|
+
border-color: var(--error);
|
|
2153
|
+
color: var(--error-contrast);
|
|
2154
|
+
font-weight: 600;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
#sendRunBtn.request-stop-active:not(:disabled):hover,
|
|
2158
|
+
#critiqueBtn.request-stop-active:not(:disabled):hover {
|
|
2159
|
+
filter: brightness(0.95);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
1924
2162
|
.file-label {
|
|
1925
2163
|
cursor: pointer;
|
|
1926
2164
|
display: inline-flex;
|
|
@@ -2034,7 +2272,7 @@ ${cssVarsBlock}
|
|
|
2034
2272
|
font-size: 13px;
|
|
2035
2273
|
line-height: 1.45;
|
|
2036
2274
|
tab-size: 2;
|
|
2037
|
-
font-family:
|
|
2275
|
+
font-family: var(--font-mono);
|
|
2038
2276
|
resize: vertical;
|
|
2039
2277
|
}
|
|
2040
2278
|
|
|
@@ -2074,13 +2312,9 @@ ${cssVarsBlock}
|
|
|
2074
2312
|
}
|
|
2075
2313
|
|
|
2076
2314
|
.sync-badge.sync {
|
|
2077
|
-
border-color: var(--
|
|
2078
|
-
color: var(--
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
.sync-badge.edited {
|
|
2082
|
-
border-color: var(--warn-border);
|
|
2083
|
-
color: var(--warn);
|
|
2315
|
+
border-color: var(--border-muted);
|
|
2316
|
+
color: var(--muted);
|
|
2317
|
+
opacity: 0.88;
|
|
2084
2318
|
}
|
|
2085
2319
|
|
|
2086
2320
|
.source-actions {
|
|
@@ -2181,7 +2415,7 @@ ${cssVarsBlock}
|
|
|
2181
2415
|
word-break: normal;
|
|
2182
2416
|
overflow-wrap: break-word;
|
|
2183
2417
|
overscroll-behavior: none;
|
|
2184
|
-
font-family:
|
|
2418
|
+
font-family: var(--font-mono);
|
|
2185
2419
|
font-size: 13px;
|
|
2186
2420
|
line-height: 1.45;
|
|
2187
2421
|
tab-size: 2;
|
|
@@ -2424,7 +2658,7 @@ ${cssVarsBlock}
|
|
|
2424
2658
|
}
|
|
2425
2659
|
|
|
2426
2660
|
.rendered-markdown code {
|
|
2427
|
-
font-family:
|
|
2661
|
+
font-family: var(--font-mono);
|
|
2428
2662
|
font-size: 0.9em;
|
|
2429
2663
|
color: var(--md-code);
|
|
2430
2664
|
}
|
|
@@ -2571,7 +2805,7 @@ ${cssVarsBlock}
|
|
|
2571
2805
|
margin: 0;
|
|
2572
2806
|
white-space: pre-wrap;
|
|
2573
2807
|
word-break: break-word;
|
|
2574
|
-
font-family:
|
|
2808
|
+
font-family: var(--font-mono);
|
|
2575
2809
|
font-size: 13px;
|
|
2576
2810
|
line-height: 1.5;
|
|
2577
2811
|
}
|
|
@@ -2580,7 +2814,7 @@ ${cssVarsBlock}
|
|
|
2580
2814
|
margin: 0;
|
|
2581
2815
|
white-space: pre-wrap;
|
|
2582
2816
|
word-break: break-word;
|
|
2583
|
-
font-family:
|
|
2817
|
+
font-family: var(--font-mono);
|
|
2584
2818
|
font-size: 13px;
|
|
2585
2819
|
line-height: 1.5;
|
|
2586
2820
|
}
|
|
@@ -2844,7 +3078,7 @@ ${cssVarsBlock}
|
|
|
2844
3078
|
<input id="resourceDirInput" type="text" placeholder="/path/to/working/directory" title="Absolute path to working directory" />
|
|
2845
3079
|
<button id="resourceDirClearBtn" type="button" title="Clear working directory">✕</button>
|
|
2846
3080
|
</span>
|
|
2847
|
-
<span id="syncBadge" class="source-badge sync-badge">
|
|
3081
|
+
<span id="syncBadge" class="source-badge sync-badge" hidden>In sync with response</span>
|
|
2848
3082
|
</div>
|
|
2849
3083
|
<div class="source-actions">
|
|
2850
3084
|
<div class="source-actions-row">
|
|
@@ -2943,16 +3177,17 @@ ${cssVarsBlock}
|
|
|
2943
3177
|
</select>
|
|
2944
3178
|
</div>
|
|
2945
3179
|
<div class="response-actions-row history-row">
|
|
2946
|
-
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">
|
|
3180
|
+
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Fetch latest response</button>
|
|
2947
3181
|
<button id="historyPrevBtn" type="button" title="Show previous response in history.">◀ Prev response</button>
|
|
2948
3182
|
<span id="historyIndexBadge" class="source-badge">History: 0/0</span>
|
|
2949
3183
|
<button id="historyNextBtn" type="button" title="Show next response in history.">Next response ▶</button>
|
|
3184
|
+
<button id="historyLastBtn" type="button" title="Jump to the latest loaded response in history.">Last response ▶|</button>
|
|
2950
3185
|
</div>
|
|
2951
3186
|
<div class="response-actions-row">
|
|
2952
|
-
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
2953
3187
|
<button id="loadResponseBtn" type="button">Load response into editor</button>
|
|
2954
3188
|
<button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
|
|
2955
3189
|
<button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
|
|
3190
|
+
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
2956
3191
|
<button id="copyResponseBtn" type="button">Copy response text</button>
|
|
2957
3192
|
</div>
|
|
2958
3193
|
</div>
|
|
@@ -3040,6 +3275,7 @@ ${cssVarsBlock}
|
|
|
3040
3275
|
const exportPdfBtn = document.getElementById("exportPdfBtn");
|
|
3041
3276
|
const historyPrevBtn = document.getElementById("historyPrevBtn");
|
|
3042
3277
|
const historyNextBtn = document.getElementById("historyNextBtn");
|
|
3278
|
+
const historyLastBtn = document.getElementById("historyLastBtn");
|
|
3043
3279
|
const historyIndexBadgeEl = document.getElementById("historyIndexBadge");
|
|
3044
3280
|
const loadHistoryPromptBtn = document.getElementById("loadHistoryPromptBtn");
|
|
3045
3281
|
const saveAsBtn = document.getElementById("saveAsBtn");
|
|
@@ -3181,7 +3417,7 @@ ${cssVarsBlock}
|
|
|
3181
3417
|
let responseHighlightEnabled = false;
|
|
3182
3418
|
let editorHighlightRenderRaf = null;
|
|
3183
3419
|
let annotationsEnabled = true;
|
|
3184
|
-
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]
|
|
3420
|
+
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]]+?)\\]/gi;
|
|
3185
3421
|
const EMPTY_OVERLAY_LINE = "\\u200b";
|
|
3186
3422
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
3187
3423
|
const MERMAID_CONFIG = ${JSON.stringify(mermaidConfig)};
|
|
@@ -3754,6 +3990,9 @@ ${cssVarsBlock}
|
|
|
3754
3990
|
if (historyNextBtn) {
|
|
3755
3991
|
historyNextBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3756
3992
|
}
|
|
3993
|
+
if (historyLastBtn) {
|
|
3994
|
+
historyLastBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3995
|
+
}
|
|
3757
3996
|
|
|
3758
3997
|
const selectedItem = getSelectedHistoryItem();
|
|
3759
3998
|
const hasPrompt = Boolean(selectedItem && typeof selectedItem.prompt === "string" && selectedItem.prompt.trim());
|
|
@@ -3930,8 +4169,9 @@ ${cssVarsBlock}
|
|
|
3930
4169
|
: latestResponseHasContent;
|
|
3931
4170
|
|
|
3932
4171
|
if (!hasComparableContent) {
|
|
3933
|
-
syncBadgeEl.
|
|
3934
|
-
syncBadgeEl.
|
|
4172
|
+
syncBadgeEl.hidden = true;
|
|
4173
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4174
|
+
syncBadgeEl.classList.remove("sync");
|
|
3935
4175
|
return;
|
|
3936
4176
|
}
|
|
3937
4177
|
|
|
@@ -3940,15 +4180,15 @@ ${cssVarsBlock}
|
|
|
3940
4180
|
: normalizeForCompare(sourceTextEl.value);
|
|
3941
4181
|
const targetNormalized = showingThinking ? latestResponseThinkingNormalized : latestResponseNormalized;
|
|
3942
4182
|
const inSync = normalizedEditor === targetNormalized;
|
|
4183
|
+
syncBadgeEl.hidden = !inSync;
|
|
4184
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4185
|
+
|
|
3943
4186
|
if (inSync) {
|
|
3944
|
-
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
3945
4187
|
syncBadgeEl.classList.add("sync");
|
|
3946
|
-
|
|
3947
|
-
} else {
|
|
3948
|
-
syncBadgeEl.textContent = showingThinking ? "Out of sync with thinking" : "Out of sync with response";
|
|
3949
|
-
syncBadgeEl.classList.add("edited");
|
|
3950
|
-
syncBadgeEl.classList.remove("sync");
|
|
4188
|
+
return;
|
|
3951
4189
|
}
|
|
4190
|
+
|
|
4191
|
+
syncBadgeEl.classList.remove("sync");
|
|
3952
4192
|
}
|
|
3953
4193
|
|
|
3954
4194
|
function buildPlainMarkdownHtml(markdown) {
|
|
@@ -4580,7 +4820,7 @@ ${cssVarsBlock}
|
|
|
4580
4820
|
}
|
|
4581
4821
|
|
|
4582
4822
|
pullLatestBtn.disabled = uiBusy || followLatest;
|
|
4583
|
-
pullLatestBtn.textContent = queuedLatestResponse ? "
|
|
4823
|
+
pullLatestBtn.textContent = queuedLatestResponse ? "Fetch latest response *" : "Fetch latest response";
|
|
4584
4824
|
|
|
4585
4825
|
updateSyncBadge(normalizedEditor);
|
|
4586
4826
|
}
|
|
@@ -4642,7 +4882,7 @@ ${cssVarsBlock}
|
|
|
4642
4882
|
saveOverBtn.disabled = uiBusy || !canSaveOver;
|
|
4643
4883
|
sendEditorBtn.disabled = uiBusy;
|
|
4644
4884
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
4645
|
-
|
|
4885
|
+
syncRunAndCritiqueButtons();
|
|
4646
4886
|
copyDraftBtn.disabled = uiBusy;
|
|
4647
4887
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
4648
4888
|
if (langSelect) langSelect.disabled = uiBusy;
|
|
@@ -4655,7 +4895,6 @@ ${cssVarsBlock}
|
|
|
4655
4895
|
followSelect.disabled = uiBusy;
|
|
4656
4896
|
if (responseHighlightSelect) responseHighlightSelect.disabled = uiBusy || rightView !== "markdown";
|
|
4657
4897
|
insertHeaderBtn.disabled = uiBusy;
|
|
4658
|
-
critiqueBtn.disabled = uiBusy;
|
|
4659
4898
|
lensSelect.disabled = uiBusy;
|
|
4660
4899
|
updateSaveFileTooltip();
|
|
4661
4900
|
updateHistoryControls();
|
|
@@ -4792,7 +5031,7 @@ ${cssVarsBlock}
|
|
|
4792
5031
|
|
|
4793
5032
|
function highlightInlineMarkdown(text) {
|
|
4794
5033
|
const source = String(text || "");
|
|
4795
|
-
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]
|
|
5034
|
+
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]]+\\])/gi;
|
|
4796
5035
|
let lastIndex = 0;
|
|
4797
5036
|
let out = "";
|
|
4798
5037
|
|
|
@@ -5352,6 +5591,51 @@ ${cssVarsBlock}
|
|
|
5352
5591
|
renderActiveResult();
|
|
5353
5592
|
}
|
|
5354
5593
|
|
|
5594
|
+
function getAbortablePendingKind() {
|
|
5595
|
+
if (!pendingRequestId) return null;
|
|
5596
|
+
return pendingKind === "direct" || pendingKind === "critique" ? pendingKind : null;
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5599
|
+
function requestCancelForPendingRequest(expectedKind) {
|
|
5600
|
+
const activeKind = getAbortablePendingKind();
|
|
5601
|
+
if (!activeKind || activeKind !== expectedKind || !pendingRequestId) {
|
|
5602
|
+
setStatus("No matching Studio request is running.", "warning");
|
|
5603
|
+
return false;
|
|
5604
|
+
}
|
|
5605
|
+
const sent = sendMessage({ type: "cancel_request", requestId: pendingRequestId });
|
|
5606
|
+
if (!sent) return false;
|
|
5607
|
+
setStatus("Stopping request…", "warning");
|
|
5608
|
+
return true;
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
function syncRunAndCritiqueButtons() {
|
|
5612
|
+
const activeKind = getAbortablePendingKind();
|
|
5613
|
+
const sendRunIsStop = activeKind === "direct";
|
|
5614
|
+
const critiqueIsStop = activeKind === "critique";
|
|
5615
|
+
|
|
5616
|
+
if (sendRunBtn) {
|
|
5617
|
+
sendRunBtn.textContent = sendRunIsStop ? "Stop" : "Run editor text";
|
|
5618
|
+
sendRunBtn.classList.toggle("request-stop-active", sendRunIsStop);
|
|
5619
|
+
sendRunBtn.disabled = sendRunIsStop ? wsState === "Disconnected" : (uiBusy || critiqueIsStop);
|
|
5620
|
+
sendRunBtn.title = sendRunIsStop
|
|
5621
|
+
? "Stop the running editor-text request."
|
|
5622
|
+
: (annotationsEnabled
|
|
5623
|
+
? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter."
|
|
5624
|
+
: "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter.");
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5627
|
+
if (critiqueBtn) {
|
|
5628
|
+
critiqueBtn.textContent = critiqueIsStop ? "Stop" : "Critique editor text";
|
|
5629
|
+
critiqueBtn.classList.toggle("request-stop-active", critiqueIsStop);
|
|
5630
|
+
critiqueBtn.disabled = critiqueIsStop ? wsState === "Disconnected" : (uiBusy || sendRunIsStop);
|
|
5631
|
+
critiqueBtn.title = critiqueIsStop
|
|
5632
|
+
? "Stop the running critique request."
|
|
5633
|
+
: (annotationsEnabled
|
|
5634
|
+
? "Critique editor text as-is (includes [an: ...] markers)."
|
|
5635
|
+
: "Critique editor text with [an: ...] markers stripped.");
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
|
|
5355
5639
|
function updateAnnotationModeUi() {
|
|
5356
5640
|
if (annotationModeSelect) {
|
|
5357
5641
|
annotationModeSelect.value = annotationsEnabled ? "on" : "off";
|
|
@@ -5360,17 +5644,7 @@ ${cssVarsBlock}
|
|
|
5360
5644
|
: "Annotations Hidden: keep markers in editor, hide in preview, and strip before Run/Critique.";
|
|
5361
5645
|
}
|
|
5362
5646
|
|
|
5363
|
-
|
|
5364
|
-
sendRunBtn.title = annotationsEnabled
|
|
5365
|
-
? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter."
|
|
5366
|
-
: "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter.";
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
if (critiqueBtn) {
|
|
5370
|
-
critiqueBtn.title = annotationsEnabled
|
|
5371
|
-
? "Critique editor text as-is (includes [an: ...] markers)."
|
|
5372
|
-
: "Critique editor text with [an: ...] markers stripped.";
|
|
5373
|
-
}
|
|
5647
|
+
syncRunAndCritiqueButtons();
|
|
5374
5648
|
}
|
|
5375
5649
|
|
|
5376
5650
|
function setAnnotationsEnabled(enabled, _options) {
|
|
@@ -5695,7 +5969,7 @@ ${cssVarsBlock}
|
|
|
5695
5969
|
if (!followLatest) {
|
|
5696
5970
|
queuedLatestResponse = payload;
|
|
5697
5971
|
updateResultActionButtons();
|
|
5698
|
-
setStatus("New response available — click
|
|
5972
|
+
setStatus("New response available — click Fetch latest response.", "warning");
|
|
5699
5973
|
return;
|
|
5700
5974
|
}
|
|
5701
5975
|
|
|
@@ -6070,7 +6344,7 @@ ${cssVarsBlock}
|
|
|
6070
6344
|
function requestLatestResponse() {
|
|
6071
6345
|
const sent = sendMessage({ type: "get_latest_response" });
|
|
6072
6346
|
if (!sent) return;
|
|
6073
|
-
setStatus("
|
|
6347
|
+
setStatus("Fetching latest response…");
|
|
6074
6348
|
}
|
|
6075
6349
|
|
|
6076
6350
|
if (leftPaneEl) {
|
|
@@ -6108,7 +6382,7 @@ ${cssVarsBlock}
|
|
|
6108
6382
|
setStatus("Applied queued response.", "success");
|
|
6109
6383
|
}
|
|
6110
6384
|
} else if (!followLatest) {
|
|
6111
|
-
setStatus("Auto-update is off. Use
|
|
6385
|
+
setStatus("Auto-update is off. Use Fetch latest response.");
|
|
6112
6386
|
}
|
|
6113
6387
|
updateResultActionButtons();
|
|
6114
6388
|
});
|
|
@@ -6192,6 +6466,16 @@ ${cssVarsBlock}
|
|
|
6192
6466
|
});
|
|
6193
6467
|
}
|
|
6194
6468
|
|
|
6469
|
+
if (historyLastBtn) {
|
|
6470
|
+
historyLastBtn.addEventListener("click", () => {
|
|
6471
|
+
if (!responseHistory.length) {
|
|
6472
|
+
setStatus("No response history available yet.", "warning");
|
|
6473
|
+
return;
|
|
6474
|
+
}
|
|
6475
|
+
selectHistoryIndex(responseHistory.length - 1);
|
|
6476
|
+
});
|
|
6477
|
+
}
|
|
6478
|
+
|
|
6195
6479
|
if (loadHistoryPromptBtn) {
|
|
6196
6480
|
loadHistoryPromptBtn.addEventListener("click", () => {
|
|
6197
6481
|
const item = getSelectedHistoryItem();
|
|
@@ -6254,6 +6538,11 @@ ${cssVarsBlock}
|
|
|
6254
6538
|
});
|
|
6255
6539
|
|
|
6256
6540
|
critiqueBtn.addEventListener("click", () => {
|
|
6541
|
+
if (getAbortablePendingKind() === "critique") {
|
|
6542
|
+
requestCancelForPendingRequest("critique");
|
|
6543
|
+
return;
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6257
6546
|
const preparedDocumentText = prepareEditorTextForSend(sourceTextEl.value);
|
|
6258
6547
|
const documentText = preparedDocumentText.trim();
|
|
6259
6548
|
if (!documentText) {
|
|
@@ -6449,6 +6738,11 @@ ${cssVarsBlock}
|
|
|
6449
6738
|
}
|
|
6450
6739
|
|
|
6451
6740
|
sendRunBtn.addEventListener("click", () => {
|
|
6741
|
+
if (getAbortablePendingKind() === "direct") {
|
|
6742
|
+
requestCancelForPendingRequest("direct");
|
|
6743
|
+
return;
|
|
6744
|
+
}
|
|
6745
|
+
|
|
6452
6746
|
const prepared = prepareEditorTextForSend(sourceTextEl.value);
|
|
6453
6747
|
if (!prepared.trim()) {
|
|
6454
6748
|
setStatus("Editor is empty. Nothing to run.", "warning");
|
|
@@ -6662,6 +6956,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6662
6956
|
let studioCwd = process.cwd();
|
|
6663
6957
|
let lastCommandCtx: ExtensionCommandContext | null = null;
|
|
6664
6958
|
let lastThemeVarsJson = "";
|
|
6959
|
+
let suppressedStudioResponse: { requestId: string; kind: StudioRequestKind } | null = null;
|
|
6665
6960
|
let agentBusy = false;
|
|
6666
6961
|
let terminalActivityPhase: TerminalActivityPhase = "idle";
|
|
6667
6962
|
let terminalActivityToolName: string | null = null;
|
|
@@ -6916,7 +7211,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
6916
7211
|
}
|
|
6917
7212
|
};
|
|
6918
7213
|
|
|
7214
|
+
const cancelActiveRequest = (requestId: string): { ok: true; kind: StudioRequestKind } | { ok: false; message: string } => {
|
|
7215
|
+
if (!activeRequest) {
|
|
7216
|
+
return { ok: false, message: "No studio request is currently running." };
|
|
7217
|
+
}
|
|
7218
|
+
if (activeRequest.id !== requestId) {
|
|
7219
|
+
return { ok: false, message: "That studio request is no longer active." };
|
|
7220
|
+
}
|
|
7221
|
+
if (!lastCommandCtx) {
|
|
7222
|
+
return { ok: false, message: "No interactive pi context is available to stop the request." };
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
const kind = activeRequest.kind;
|
|
7226
|
+
try {
|
|
7227
|
+
lastCommandCtx.abort();
|
|
7228
|
+
} catch (error) {
|
|
7229
|
+
return {
|
|
7230
|
+
ok: false,
|
|
7231
|
+
message: `Failed to stop request: ${error instanceof Error ? error.message : String(error)}`,
|
|
7232
|
+
};
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
suppressedStudioResponse = { requestId, kind };
|
|
7236
|
+
emitDebugEvent("cancel_active_request", { requestId, kind });
|
|
7237
|
+
clearActiveRequest({ notify: "Cancelled request.", level: "warning" });
|
|
7238
|
+
return { ok: true, kind };
|
|
7239
|
+
};
|
|
7240
|
+
|
|
6919
7241
|
const beginRequest = (requestId: string, kind: StudioRequestKind): boolean => {
|
|
7242
|
+
suppressedStudioResponse = null;
|
|
6920
7243
|
emitDebugEvent("begin_request_attempt", {
|
|
6921
7244
|
requestId,
|
|
6922
7245
|
kind,
|
|
@@ -7024,6 +7347,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
7024
7347
|
return;
|
|
7025
7348
|
}
|
|
7026
7349
|
|
|
7350
|
+
if (msg.type === "cancel_request") {
|
|
7351
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
7352
|
+
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
7353
|
+
return;
|
|
7354
|
+
}
|
|
7355
|
+
|
|
7356
|
+
const result = cancelActiveRequest(msg.requestId);
|
|
7357
|
+
if (!result.ok) {
|
|
7358
|
+
sendToClient(client, { type: "error", requestId: msg.requestId, message: result.message });
|
|
7359
|
+
}
|
|
7360
|
+
return;
|
|
7361
|
+
}
|
|
7362
|
+
|
|
7027
7363
|
if (msg.type === "critique_request") {
|
|
7028
7364
|
if (!isValidRequestId(msg.requestId)) {
|
|
7029
7365
|
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
@@ -7856,6 +8192,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
7856
8192
|
|
|
7857
8193
|
if (!markdown) return;
|
|
7858
8194
|
|
|
8195
|
+
if (suppressedStudioResponse) {
|
|
8196
|
+
emitDebugEvent("suppressed_cancelled_response", {
|
|
8197
|
+
requestId: suppressedStudioResponse.requestId,
|
|
8198
|
+
kind: suppressedStudioResponse.kind,
|
|
8199
|
+
markdownLength: markdown.length,
|
|
8200
|
+
thinkingLength: thinking ? thinking.length : 0,
|
|
8201
|
+
});
|
|
8202
|
+
return;
|
|
8203
|
+
}
|
|
8204
|
+
|
|
7859
8205
|
syncStudioResponseHistory(ctx.sessionManager.getBranch());
|
|
7860
8206
|
refreshContextUsage(ctx);
|
|
7861
8207
|
const latestHistoryItem = studioResponseHistory[studioResponseHistory.length - 1];
|
|
@@ -7936,7 +8282,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
7936
8282
|
pi.on("agent_end", async () => {
|
|
7937
8283
|
agentBusy = false;
|
|
7938
8284
|
refreshContextUsage();
|
|
7939
|
-
emitDebugEvent("agent_end", {
|
|
8285
|
+
emitDebugEvent("agent_end", {
|
|
8286
|
+
activeRequestId: activeRequest?.id ?? null,
|
|
8287
|
+
activeRequestKind: activeRequest?.kind ?? null,
|
|
8288
|
+
suppressedRequestId: suppressedStudioResponse?.requestId ?? null,
|
|
8289
|
+
suppressedRequestKind: suppressedStudioResponse?.kind ?? null,
|
|
8290
|
+
});
|
|
7940
8291
|
setTerminalActivity("idle");
|
|
7941
8292
|
if (activeRequest) {
|
|
7942
8293
|
const requestId = activeRequest.id;
|
|
@@ -7947,6 +8298,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7947
8298
|
});
|
|
7948
8299
|
clearActiveRequest();
|
|
7949
8300
|
}
|
|
8301
|
+
suppressedStudioResponse = null;
|
|
7950
8302
|
});
|
|
7951
8303
|
|
|
7952
8304
|
pi.on("session_shutdown", async () => {
|