pi-studio 0.5.28 → 0.5.30
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 +13 -0
- package/client/studio-client.js +240 -45
- package/client/studio.css +10 -3
- package/index.ts +515 -39
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ type RequestedLens = Lens | "auto";
|
|
|
14
14
|
type StudioRequestKind = "critique" | "annotation" | "direct" | "compact";
|
|
15
15
|
type StudioSourceKind = "file" | "last-response" | "blank";
|
|
16
16
|
type TerminalActivityPhase = "idle" | "running" | "tool" | "responding";
|
|
17
|
+
type StudioPromptMode = "response" | "run" | "effective";
|
|
18
|
+
type StudioPromptTriggerKind = "run" | "steer";
|
|
17
19
|
|
|
18
20
|
const STUDIO_CSS_URL = new URL("./client/studio.css", import.meta.url);
|
|
19
21
|
const STUDIO_CLIENT_URL = new URL("./client/studio-client.js", import.meta.url);
|
|
@@ -26,10 +28,17 @@ interface StudioServerState {
|
|
|
26
28
|
token: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
interface
|
|
31
|
+
interface StudioPromptDescriptor {
|
|
32
|
+
prompt: string | null;
|
|
33
|
+
promptMode: StudioPromptMode;
|
|
34
|
+
promptTriggerKind: StudioPromptTriggerKind | null;
|
|
35
|
+
promptSteeringCount: number;
|
|
36
|
+
promptTriggerText: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ActiveStudioRequest extends StudioPromptDescriptor {
|
|
30
40
|
id: string;
|
|
31
41
|
kind: StudioRequestKind;
|
|
32
|
-
prompt: string | null;
|
|
33
42
|
timer: NodeJS.Timeout;
|
|
34
43
|
startedAt: number;
|
|
35
44
|
}
|
|
@@ -41,13 +50,28 @@ interface LastStudioResponse {
|
|
|
41
50
|
kind: StudioRequestKind;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
interface StudioResponseHistoryItem {
|
|
53
|
+
interface StudioResponseHistoryItem extends StudioPromptDescriptor {
|
|
45
54
|
id: string;
|
|
46
55
|
markdown: string;
|
|
47
56
|
thinking: string | null;
|
|
48
57
|
timestamp: number;
|
|
49
58
|
kind: StudioRequestKind;
|
|
50
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface StudioDirectRunChain {
|
|
62
|
+
id: string;
|
|
63
|
+
basePrompt: string;
|
|
64
|
+
steeringPrompts: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface QueuedStudioDirectRequest extends StudioPromptDescriptor {
|
|
68
|
+
requestId: string;
|
|
69
|
+
queuedAt: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PersistedStudioPromptMetadata extends StudioPromptDescriptor {
|
|
73
|
+
version: 1;
|
|
74
|
+
requestKind: "direct";
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
interface StudioContextUsageSnapshot {
|
|
@@ -173,6 +197,7 @@ const STUDIO_TERMINAL_NOTIFY_TITLE = "pi Studio";
|
|
|
173
197
|
const CMUX_STUDIO_STATUS_KEY = "pi_studio";
|
|
174
198
|
const CMUX_STUDIO_STATUS_COLOR_DARK = "#5ea1ff";
|
|
175
199
|
const CMUX_STUDIO_STATUS_COLOR_LIGHT = "#0047ab";
|
|
200
|
+
const STUDIO_PROMPT_METADATA_CUSTOM_TYPE = "pi-studio/direct-prompt";
|
|
176
201
|
|
|
177
202
|
const PDF_PREAMBLE = `\\usepackage{titlesec}
|
|
178
203
|
\\titleformat{\\section}{\\Large\\bfseries\\sffamily}{}{0pt}{}[\\vspace{3pt}\\titlerule\\vspace{12pt}]
|
|
@@ -180,6 +205,11 @@ const PDF_PREAMBLE = `\\usepackage{titlesec}
|
|
|
180
205
|
\\titleformat{\\subsubsection}{\\normalsize\\bfseries\\sffamily}{}{0pt}{}
|
|
181
206
|
\\titlespacing*{\\section}{0pt}{1.5ex plus 0.5ex minus 0.2ex}{1ex plus 0.2ex}
|
|
182
207
|
\\titlespacing*{\\subsection}{0pt}{1.2ex plus 0.4ex minus 0.2ex}{0.6ex plus 0.1ex}
|
|
208
|
+
\\usepackage{xcolor}
|
|
209
|
+
\\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
|
|
210
|
+
\\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
|
|
211
|
+
\\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
|
|
212
|
+
\\newcommand{\\studioannotation}[1]{\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}}\\endgroup}
|
|
183
213
|
\\usepackage{caption}
|
|
184
214
|
\\captionsetup[figure]{justification=raggedright,singlelinecheck=false}
|
|
185
215
|
\\usepackage{enumitem}
|
|
@@ -2441,6 +2471,9 @@ function isLikelyMathExpression(expr: string): boolean {
|
|
|
2441
2471
|
|
|
2442
2472
|
function collapseDisplayMathContent(expr: string): string {
|
|
2443
2473
|
let content = expr.trim();
|
|
2474
|
+
if (/\\begin\{[^}]+\}|\\end\{[^}]+\}/.test(content)) {
|
|
2475
|
+
return content;
|
|
2476
|
+
}
|
|
2444
2477
|
if (content.includes("\\\\") || content.includes("\n")) {
|
|
2445
2478
|
content = content.replace(/\\\\\s*/g, " ");
|
|
2446
2479
|
content = content.replace(/\s*\n\s*/g, " ");
|
|
@@ -2621,6 +2654,85 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
2621
2654
|
return undefined;
|
|
2622
2655
|
}
|
|
2623
2656
|
|
|
2657
|
+
function escapeStudioPdfLatexText(text: string): string {
|
|
2658
|
+
return String(text ?? "")
|
|
2659
|
+
.replace(/\r\n/g, "\n")
|
|
2660
|
+
.replace(/\s*\n\s*/g, " ")
|
|
2661
|
+
.trim()
|
|
2662
|
+
.replace(/\\/g, "\\textbackslash{}")
|
|
2663
|
+
.replace(/([{}%#$&_])/g, "\\$1")
|
|
2664
|
+
.replace(/~/g, "\\textasciitilde{}")
|
|
2665
|
+
.replace(/\^/g, "\\textasciicircum{}")
|
|
2666
|
+
.replace(/\s{2,}/g, " ");
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
function replaceStudioAnnotationMarkersForPdfInSegment(text: string): string {
|
|
2670
|
+
return String(text ?? "")
|
|
2671
|
+
.replace(/\[an:\s*([^\]]+?)\]/gi, (_match, markerText: string) => {
|
|
2672
|
+
const cleaned = escapeStudioPdfLatexText(markerText);
|
|
2673
|
+
if (!cleaned) return "";
|
|
2674
|
+
return `\\studioannotation{${cleaned}}`;
|
|
2675
|
+
})
|
|
2676
|
+
.replace(/\{\[\}\s*an:\s*([\s\S]*?)\s*\{\]\}/gi, (_match, markerText: string) => {
|
|
2677
|
+
const cleaned = escapeStudioPdfLatexText(markerText);
|
|
2678
|
+
if (!cleaned) return "";
|
|
2679
|
+
return `\\studioannotation{${cleaned}}`;
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function replaceStudioAnnotationMarkersForPdf(markdown: string): string {
|
|
2684
|
+
const lines = String(markdown ?? "").split("\n");
|
|
2685
|
+
const out: string[] = [];
|
|
2686
|
+
let plainBuffer: string[] = [];
|
|
2687
|
+
let inFence = false;
|
|
2688
|
+
let fenceChar: "`" | "~" | undefined;
|
|
2689
|
+
let fenceLength = 0;
|
|
2690
|
+
|
|
2691
|
+
const flushPlain = () => {
|
|
2692
|
+
if (plainBuffer.length === 0) return;
|
|
2693
|
+
out.push(replaceStudioAnnotationMarkersForPdfInSegment(plainBuffer.join("\n")));
|
|
2694
|
+
plainBuffer = [];
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2697
|
+
for (const line of lines) {
|
|
2698
|
+
const trimmed = line.trimStart();
|
|
2699
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
2700
|
+
|
|
2701
|
+
if (fenceMatch) {
|
|
2702
|
+
const marker = fenceMatch[1]!;
|
|
2703
|
+
const markerChar = marker[0] as "`" | "~";
|
|
2704
|
+
const markerLength = marker.length;
|
|
2705
|
+
|
|
2706
|
+
if (!inFence) {
|
|
2707
|
+
flushPlain();
|
|
2708
|
+
inFence = true;
|
|
2709
|
+
fenceChar = markerChar;
|
|
2710
|
+
fenceLength = markerLength;
|
|
2711
|
+
out.push(line);
|
|
2712
|
+
continue;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
2716
|
+
inFence = false;
|
|
2717
|
+
fenceChar = undefined;
|
|
2718
|
+
fenceLength = 0;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
out.push(line);
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
if (inFence) {
|
|
2726
|
+
out.push(line);
|
|
2727
|
+
} else {
|
|
2728
|
+
plainBuffer.push(line);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
flushPlain();
|
|
2733
|
+
return out.join("\n");
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2624
2736
|
function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLanguage?: string): string {
|
|
2625
2737
|
if (isLatex) return markdown;
|
|
2626
2738
|
const effectiveEditorLanguage = inferStudioPdfLanguage(markdown, editorLanguage);
|
|
@@ -2628,7 +2740,10 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
2628
2740
|
&& !isStudioSingleFencedCodeBlock(markdown)
|
|
2629
2741
|
? wrapStudioCodeAsMarkdown(markdown, effectiveEditorLanguage)
|
|
2630
2742
|
: markdown;
|
|
2631
|
-
|
|
2743
|
+
const annotationReadySource = !effectiveEditorLanguage || effectiveEditorLanguage === "markdown" || effectiveEditorLanguage === "latex"
|
|
2744
|
+
? replaceStudioAnnotationMarkersForPdf(source)
|
|
2745
|
+
: source;
|
|
2746
|
+
return normalizeObsidianImages(normalizeMathDelimiters(annotationReadySource));
|
|
2632
2747
|
}
|
|
2633
2748
|
|
|
2634
2749
|
function stripMathMlAnnotationTags(html: string): string {
|
|
@@ -2955,6 +3070,46 @@ async function renderStudioLiteralTextPdf(text: string, title = "Studio export")
|
|
|
2955
3070
|
}
|
|
2956
3071
|
}
|
|
2957
3072
|
|
|
3073
|
+
function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
|
|
3074
|
+
const lines = String(latex ?? "").split("\n");
|
|
3075
|
+
const out: string[] = [];
|
|
3076
|
+
const rawEnvStack: string[] = [];
|
|
3077
|
+
const rawEnvNames = new Set(["verbatim", "Verbatim", "Highlighting", "lstlisting"]);
|
|
3078
|
+
|
|
3079
|
+
const updateRawEnvStack = (line: string) => {
|
|
3080
|
+
const envPattern = /\\(begin|end)\{([^}]+)\}/g;
|
|
3081
|
+
let match: RegExpExecArray | null;
|
|
3082
|
+
while ((match = envPattern.exec(line)) !== null) {
|
|
3083
|
+
const kind = match[1];
|
|
3084
|
+
const envName = match[2];
|
|
3085
|
+
if (!envName || !rawEnvNames.has(envName)) continue;
|
|
3086
|
+
if (kind === "begin") {
|
|
3087
|
+
rawEnvStack.push(envName);
|
|
3088
|
+
} else {
|
|
3089
|
+
for (let i = rawEnvStack.length - 1; i >= 0; i -= 1) {
|
|
3090
|
+
if (rawEnvStack[i] === envName) {
|
|
3091
|
+
rawEnvStack.splice(i, 1);
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
for (const line of lines) {
|
|
3100
|
+
if (rawEnvStack.length > 0) {
|
|
3101
|
+
out.push(line);
|
|
3102
|
+
updateRawEnvStack(line);
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
out.push(replaceStudioAnnotationMarkersForPdfInSegment(line));
|
|
3107
|
+
updateRawEnvStack(line);
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
return out.join("\n");
|
|
3111
|
+
}
|
|
3112
|
+
|
|
2958
3113
|
async function renderStudioPdfFromGeneratedLatex(
|
|
2959
3114
|
markdown: string,
|
|
2960
3115
|
pandocCommand: string,
|
|
@@ -3032,7 +3187,8 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
3032
3187
|
|
|
3033
3188
|
const generatedLatex = await readFile(latexPath, "utf-8");
|
|
3034
3189
|
const injectedLatex = injectStudioLatexPdfSubfigureBlocks(generatedLatex, subfigureGroups, sourcePath, resourcePath);
|
|
3035
|
-
const
|
|
3190
|
+
const annotationReadyLatex = replaceStudioAnnotationMarkersInGeneratedLatex(injectedLatex);
|
|
3191
|
+
const normalizedLatex = normalizeStudioGeneratedFigureCaptions(annotationReadyLatex);
|
|
3036
3192
|
await writeFile(latexPath, normalizedLatex, "utf-8");
|
|
3037
3193
|
|
|
3038
3194
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -3194,7 +3350,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
3194
3350
|
}
|
|
3195
3351
|
};
|
|
3196
3352
|
|
|
3197
|
-
if (isLatex && latexSubfigurePdfTransform.groups.length > 0) {
|
|
3353
|
+
if (isLatex && (latexSubfigurePdfTransform.groups.length > 0 || /\[an:\s*[^\]]+\]/i.test(sourceWithResolvedRefs))) {
|
|
3198
3354
|
return await renderStudioPdfFromGeneratedLatex(
|
|
3199
3355
|
sourceWithResolvedRefs,
|
|
3200
3356
|
pandocCommand,
|
|
@@ -3617,6 +3773,95 @@ function normalizePromptText(text: string | null | undefined): string | null {
|
|
|
3617
3773
|
return trimmed.length > 0 ? trimmed : null;
|
|
3618
3774
|
}
|
|
3619
3775
|
|
|
3776
|
+
function buildStudioPromptDescriptor(
|
|
3777
|
+
prompt: string | null,
|
|
3778
|
+
promptMode: StudioPromptMode = "response",
|
|
3779
|
+
promptTriggerKind: StudioPromptTriggerKind | null = null,
|
|
3780
|
+
promptSteeringCount = 0,
|
|
3781
|
+
promptTriggerText: string | null = null,
|
|
3782
|
+
): StudioPromptDescriptor {
|
|
3783
|
+
return {
|
|
3784
|
+
prompt: normalizePromptText(prompt),
|
|
3785
|
+
promptMode,
|
|
3786
|
+
promptTriggerKind,
|
|
3787
|
+
promptSteeringCount: Number.isFinite(promptSteeringCount) && promptSteeringCount > 0
|
|
3788
|
+
? Math.max(0, Math.floor(promptSteeringCount))
|
|
3789
|
+
: 0,
|
|
3790
|
+
promptTriggerText: normalizePromptText(promptTriggerText),
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
function buildStudioEffectivePrompt(basePrompt: string | null | undefined, steeringPrompts: Array<string | null | undefined>): string | null {
|
|
3795
|
+
const normalizedBasePrompt = normalizePromptText(basePrompt);
|
|
3796
|
+
const normalizedSteeringPrompts = steeringPrompts
|
|
3797
|
+
.map((prompt) => normalizePromptText(prompt))
|
|
3798
|
+
.filter((prompt): prompt is string => Boolean(prompt));
|
|
3799
|
+
|
|
3800
|
+
if (!normalizedBasePrompt) {
|
|
3801
|
+
if (normalizedSteeringPrompts.length === 0) return null;
|
|
3802
|
+
return normalizedSteeringPrompts.join("\n\n");
|
|
3803
|
+
}
|
|
3804
|
+
if (normalizedSteeringPrompts.length === 0) return normalizedBasePrompt;
|
|
3805
|
+
|
|
3806
|
+
const sections = ["## Original run prompt\n\n" + normalizedBasePrompt];
|
|
3807
|
+
for (let i = 0; i < normalizedSteeringPrompts.length; i++) {
|
|
3808
|
+
sections.push(`## Steering ${i + 1}\n\n${normalizedSteeringPrompts[i]}`);
|
|
3809
|
+
}
|
|
3810
|
+
return sections.join("\n\n").trim();
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
function buildStudioDirectRunPromptDescriptor(prompt: string): StudioPromptDescriptor {
|
|
3814
|
+
const normalizedPrompt = normalizePromptText(prompt);
|
|
3815
|
+
return buildStudioPromptDescriptor(normalizedPrompt, "run", "run", 0, normalizedPrompt);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
function buildStudioQueuedSteerPromptDescriptor(chain: StudioDirectRunChain, triggerPrompt: string): StudioPromptDescriptor {
|
|
3819
|
+
const normalizedTriggerPrompt = normalizePromptText(triggerPrompt);
|
|
3820
|
+
const steeringPrompts = [...chain.steeringPrompts, normalizedTriggerPrompt].filter((prompt): prompt is string => Boolean(prompt));
|
|
3821
|
+
const effectivePrompt = buildStudioEffectivePrompt(chain.basePrompt, steeringPrompts);
|
|
3822
|
+
return buildStudioPromptDescriptor(effectivePrompt, "effective", "steer", steeringPrompts.length, normalizedTriggerPrompt);
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
function buildPersistedStudioPromptMetadata(promptDescriptor: StudioPromptDescriptor): PersistedStudioPromptMetadata {
|
|
3826
|
+
return {
|
|
3827
|
+
version: 1,
|
|
3828
|
+
requestKind: "direct",
|
|
3829
|
+
prompt: promptDescriptor.prompt,
|
|
3830
|
+
promptMode: promptDescriptor.promptMode,
|
|
3831
|
+
promptTriggerKind: promptDescriptor.promptTriggerKind,
|
|
3832
|
+
promptSteeringCount: promptDescriptor.promptSteeringCount,
|
|
3833
|
+
promptTriggerText: promptDescriptor.promptTriggerText,
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
function extractPersistedStudioPromptMetadata(entry: SessionEntry): PersistedStudioPromptMetadata | null {
|
|
3838
|
+
if (!entry || entry.type !== "custom") return null;
|
|
3839
|
+
const customEntry = entry as { customType?: unknown; data?: unknown };
|
|
3840
|
+
if (customEntry.customType !== STUDIO_PROMPT_METADATA_CUSTOM_TYPE) return null;
|
|
3841
|
+
const data = customEntry.data as Partial<PersistedStudioPromptMetadata> | undefined;
|
|
3842
|
+
if (!data || data.requestKind !== "direct") return null;
|
|
3843
|
+
return {
|
|
3844
|
+
version: data.version === 1 ? 1 : 1,
|
|
3845
|
+
requestKind: "direct",
|
|
3846
|
+
...buildStudioPromptDescriptor(
|
|
3847
|
+
typeof data.prompt === "string" ? data.prompt : null,
|
|
3848
|
+
data.promptMode === "run" || data.promptMode === "effective" ? data.promptMode : "response",
|
|
3849
|
+
data.promptTriggerKind === "run" || data.promptTriggerKind === "steer" ? data.promptTriggerKind : null,
|
|
3850
|
+
typeof data.promptSteeringCount === "number" ? data.promptSteeringCount : 0,
|
|
3851
|
+
typeof data.promptTriggerText === "string" ? data.promptTriggerText : null,
|
|
3852
|
+
),
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function getStudioPromptSourceLabel(promptMode: StudioPromptMode, promptSteeringCount: number): string | null {
|
|
3857
|
+
if (promptMode === "run") return "original run";
|
|
3858
|
+
if (promptMode !== "effective") return null;
|
|
3859
|
+
if (promptSteeringCount <= 0) return "original run";
|
|
3860
|
+
return promptSteeringCount === 1
|
|
3861
|
+
? "original run + 1 steering message"
|
|
3862
|
+
: `original run + ${promptSteeringCount} steering messages`;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3620
3865
|
function extractUserText(message: unknown): string | null {
|
|
3621
3866
|
const msg = message as {
|
|
3622
3867
|
role?: string;
|
|
@@ -3673,27 +3918,49 @@ function parseEntryTimestamp(timestamp: unknown): number {
|
|
|
3673
3918
|
function buildResponseHistoryFromEntries(entries: SessionEntry[], limit = RESPONSE_HISTORY_LIMIT): StudioResponseHistoryItem[] {
|
|
3674
3919
|
const history: StudioResponseHistoryItem[] = [];
|
|
3675
3920
|
let lastUserPrompt: string | null = null;
|
|
3921
|
+
let pendingPromptDescriptor: StudioPromptDescriptor | null = null;
|
|
3676
3922
|
|
|
3677
3923
|
for (const entry of entries) {
|
|
3678
|
-
if (!entry
|
|
3924
|
+
if (!entry) continue;
|
|
3925
|
+
|
|
3926
|
+
const persistedPromptMetadata = extractPersistedStudioPromptMetadata(entry);
|
|
3927
|
+
if (persistedPromptMetadata) {
|
|
3928
|
+
pendingPromptDescriptor = buildStudioPromptDescriptor(
|
|
3929
|
+
persistedPromptMetadata.prompt,
|
|
3930
|
+
persistedPromptMetadata.promptMode,
|
|
3931
|
+
persistedPromptMetadata.promptTriggerKind,
|
|
3932
|
+
persistedPromptMetadata.promptSteeringCount,
|
|
3933
|
+
persistedPromptMetadata.promptTriggerText,
|
|
3934
|
+
);
|
|
3935
|
+
continue;
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3938
|
+
if (entry.type !== "message") continue;
|
|
3679
3939
|
const message = (entry as { message?: unknown }).message;
|
|
3680
3940
|
const role = (message as { role?: string } | undefined)?.role;
|
|
3681
3941
|
if (role === "user") {
|
|
3682
3942
|
lastUserPrompt = extractUserText(message);
|
|
3943
|
+
pendingPromptDescriptor = null;
|
|
3683
3944
|
continue;
|
|
3684
3945
|
}
|
|
3685
3946
|
if (role !== "assistant") continue;
|
|
3686
3947
|
const markdown = extractAssistantText(message);
|
|
3687
3948
|
if (!markdown) continue;
|
|
3688
3949
|
const thinking = extractAssistantThinking(message);
|
|
3950
|
+
const promptDescriptor = pendingPromptDescriptor ?? buildStudioPromptDescriptor(lastUserPrompt);
|
|
3689
3951
|
history.push({
|
|
3690
3952
|
id: typeof (entry as { id?: unknown }).id === "string" ? (entry as { id: string }).id : randomUUID(),
|
|
3691
3953
|
markdown,
|
|
3692
3954
|
thinking,
|
|
3693
3955
|
timestamp: parseEntryTimestamp((entry as { timestamp?: unknown }).timestamp),
|
|
3694
3956
|
kind: inferStudioResponseKind(markdown),
|
|
3695
|
-
prompt:
|
|
3957
|
+
prompt: promptDescriptor.prompt,
|
|
3958
|
+
promptMode: promptDescriptor.promptMode,
|
|
3959
|
+
promptTriggerKind: promptDescriptor.promptTriggerKind,
|
|
3960
|
+
promptSteeringCount: promptDescriptor.promptSteeringCount,
|
|
3961
|
+
promptTriggerText: promptDescriptor.promptTriggerText,
|
|
3696
3962
|
});
|
|
3963
|
+
pendingPromptDescriptor = null;
|
|
3697
3964
|
}
|
|
3698
3965
|
|
|
3699
3966
|
if (history.length <= limit) return history;
|
|
@@ -4206,7 +4473,7 @@ ${cssVarsBlock}
|
|
|
4206
4473
|
</select>
|
|
4207
4474
|
</div>
|
|
4208
4475
|
<div class="section-header-actions">
|
|
4209
|
-
<button id="leftFocusBtn" class="pane-focus-btn" type="button" title="Show only the editor pane. Shortcut: Cmd/Ctrl+Esc
|
|
4476
|
+
<button id="leftFocusBtn" class="pane-focus-btn" type="button" title="Show only the editor pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
4210
4477
|
</div>
|
|
4211
4478
|
</div>
|
|
4212
4479
|
<div class="source-wrap">
|
|
@@ -4223,7 +4490,8 @@ ${cssVarsBlock}
|
|
|
4223
4490
|
</div>
|
|
4224
4491
|
<div class="source-actions">
|
|
4225
4492
|
<div class="source-actions-row">
|
|
4226
|
-
<button id="sendRunBtn" type="button" title="
|
|
4493
|
+
<button id="sendRunBtn" type="button" title="Run editor text. While a direct run is active, this button becomes Stop. Cmd/Ctrl+Enter queues steering from the current editor text. Stop the active request with Esc.">Run editor text</button>
|
|
4494
|
+
<button id="queueSteerBtn" type="button" title="Queue steering is available while Run editor text is active." disabled>Queue steering</button>
|
|
4227
4495
|
<button id="copyDraftBtn" type="button">Copy editor text</button>
|
|
4228
4496
|
<button id="sendEditorBtn" type="button">Send to pi editor</button>
|
|
4229
4497
|
</div>
|
|
@@ -4296,7 +4564,7 @@ ${cssVarsBlock}
|
|
|
4296
4564
|
</select>
|
|
4297
4565
|
</div>
|
|
4298
4566
|
<div class="section-header-actions">
|
|
4299
|
-
<button id="rightFocusBtn" class="pane-focus-btn" type="button" title="Show only the response pane. Shortcut: Cmd/Ctrl+Esc
|
|
4567
|
+
<button id="rightFocusBtn" class="pane-focus-btn" type="button" title="Show only the response pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
4300
4568
|
<button id="exportPdfBtn" type="button" title="Export the current right-pane preview as PDF via pandoc + xelatex.">Export right preview as PDF</button>
|
|
4301
4569
|
</div>
|
|
4302
4570
|
</div>
|
|
@@ -4338,7 +4606,7 @@ ${cssVarsBlock}
|
|
|
4338
4606
|
<footer>
|
|
4339
4607
|
<span id="statusLine"><span id="statusSpinner" aria-hidden="true"> </span><span id="status">Booting studio…</span></span>
|
|
4340
4608
|
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text">Model: ${initialModel} · Terminal: ${initialTerminal} · Context: unknown</span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact</button></span>
|
|
4341
|
-
<span class="shortcut-hint">Focus pane: Cmd/Ctrl+Esc
|
|
4609
|
+
<span class="shortcut-hint">Focus pane: F10 (or Cmd/Ctrl+Esc) to toggle · Run / queue steering: Cmd/Ctrl+Enter · Stop request: Esc</span>
|
|
4342
4610
|
</footer>
|
|
4343
4611
|
|
|
4344
4612
|
<!-- Defer sanitizer script so studio can boot/connect even if CDN is slow or blocked. -->
|
|
@@ -4354,6 +4622,9 @@ ${cssVarsBlock}
|
|
|
4354
4622
|
export default function (pi: ExtensionAPI) {
|
|
4355
4623
|
let serverState: StudioServerState | null = null;
|
|
4356
4624
|
let activeRequest: ActiveStudioRequest | null = null;
|
|
4625
|
+
let studioDirectRunChain: StudioDirectRunChain | null = null;
|
|
4626
|
+
let queuedStudioDirectRequests: QueuedStudioDirectRequest[] = [];
|
|
4627
|
+
let pendingStudioPromptMetadata: StudioPromptDescriptor | null = null;
|
|
4357
4628
|
let lastStudioResponse: LastStudioResponse | null = null;
|
|
4358
4629
|
let preparedPdfExports = new Map<string, PreparedStudioPdfExport>();
|
|
4359
4630
|
let initialStudioDocument: InitialStudioDocument | null = null;
|
|
@@ -4386,6 +4657,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
4386
4657
|
const installedPackageVersion = packageMetadata?.version ?? null;
|
|
4387
4658
|
let updateAvailableLatestVersion: string | null = null;
|
|
4388
4659
|
|
|
4660
|
+
const isStudioDirectRunChainActive = () => Boolean(studioDirectRunChain);
|
|
4661
|
+
const getQueuedStudioSteeringCount = () => queuedStudioDirectRequests.length;
|
|
4662
|
+
const canQueueStudioSteeringRequest = () => {
|
|
4663
|
+
if (compactInProgress) return false;
|
|
4664
|
+
if (!agentBusy) return false;
|
|
4665
|
+
if (!studioDirectRunChain) return false;
|
|
4666
|
+
return !activeRequest || activeRequest.kind === "direct";
|
|
4667
|
+
};
|
|
4668
|
+
const clearStudioDirectRunState = () => {
|
|
4669
|
+
studioDirectRunChain = null;
|
|
4670
|
+
queuedStudioDirectRequests = [];
|
|
4671
|
+
pendingStudioPromptMetadata = null;
|
|
4672
|
+
};
|
|
4673
|
+
|
|
4389
4674
|
const isStudioBusy = () => agentBusy || activeRequest !== null || compactInProgress;
|
|
4390
4675
|
|
|
4391
4676
|
const getSessionNameSafe = (): string | undefined => {
|
|
@@ -4862,6 +5147,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
4862
5147
|
compactInProgress,
|
|
4863
5148
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
4864
5149
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
5150
|
+
studioRunChainActive: isStudioDirectRunChainActive(),
|
|
5151
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
4865
5152
|
});
|
|
4866
5153
|
};
|
|
4867
5154
|
|
|
@@ -4916,19 +5203,67 @@ export default function (pi: ExtensionAPI) {
|
|
|
4916
5203
|
};
|
|
4917
5204
|
}
|
|
4918
5205
|
|
|
5206
|
+
if (kind === "direct") {
|
|
5207
|
+
clearStudioDirectRunState();
|
|
5208
|
+
}
|
|
4919
5209
|
suppressedStudioResponse = { requestId, kind };
|
|
4920
|
-
emitDebugEvent("cancel_active_request", { requestId, kind });
|
|
5210
|
+
emitDebugEvent("cancel_active_request", { requestId, kind, queuedSteeringCount: getQueuedStudioSteeringCount() });
|
|
4921
5211
|
clearActiveRequest({ notify: "Cancelled request.", level: "warning" });
|
|
4922
5212
|
return { ok: true, kind };
|
|
4923
5213
|
};
|
|
4924
5214
|
|
|
4925
|
-
const
|
|
5215
|
+
const activateRequest = (
|
|
5216
|
+
requestId: string,
|
|
5217
|
+
kind: StudioRequestKind,
|
|
5218
|
+
promptDescriptor?: StudioPromptDescriptor | null,
|
|
5219
|
+
options?: { skipNotificationCleanup?: boolean },
|
|
5220
|
+
): boolean => {
|
|
5221
|
+
const descriptor = promptDescriptor ?? buildStudioPromptDescriptor(null);
|
|
5222
|
+
const timer = setTimeout(() => {
|
|
5223
|
+
if (!activeRequest || activeRequest.id !== requestId) return;
|
|
5224
|
+
emitDebugEvent("request_timeout", { requestId, kind });
|
|
5225
|
+
broadcast({ type: "error", requestId, message: "Studio request timed out. Please try again." });
|
|
5226
|
+
clearActiveRequest();
|
|
5227
|
+
}, REQUEST_TIMEOUT_MS);
|
|
5228
|
+
|
|
5229
|
+
activeRequest = {
|
|
5230
|
+
id: requestId,
|
|
5231
|
+
kind,
|
|
5232
|
+
prompt: descriptor.prompt,
|
|
5233
|
+
promptMode: descriptor.promptMode,
|
|
5234
|
+
promptTriggerKind: descriptor.promptTriggerKind,
|
|
5235
|
+
promptSteeringCount: descriptor.promptSteeringCount,
|
|
5236
|
+
promptTriggerText: descriptor.promptTriggerText,
|
|
5237
|
+
startedAt: Date.now(),
|
|
5238
|
+
timer,
|
|
5239
|
+
};
|
|
5240
|
+
if (!options?.skipNotificationCleanup) {
|
|
5241
|
+
maybeClearStaleCmuxStudioNotifications();
|
|
5242
|
+
}
|
|
5243
|
+
syncCmuxStudioStatus();
|
|
5244
|
+
|
|
5245
|
+
emitDebugEvent("begin_request", {
|
|
5246
|
+
requestId,
|
|
5247
|
+
kind,
|
|
5248
|
+
promptMode: descriptor.promptMode,
|
|
5249
|
+
promptTriggerKind: descriptor.promptTriggerKind,
|
|
5250
|
+
promptSteeringCount: descriptor.promptSteeringCount,
|
|
5251
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
5252
|
+
});
|
|
5253
|
+
broadcast({ type: "request_started", requestId, kind });
|
|
5254
|
+
broadcastState();
|
|
5255
|
+
return true;
|
|
5256
|
+
};
|
|
5257
|
+
|
|
5258
|
+
const beginRequest = (requestId: string, kind: StudioRequestKind, promptDescriptor?: StudioPromptDescriptor | null): boolean => {
|
|
4926
5259
|
suppressedStudioResponse = null;
|
|
4927
5260
|
emitDebugEvent("begin_request_attempt", {
|
|
4928
5261
|
requestId,
|
|
4929
5262
|
kind,
|
|
4930
5263
|
hasActiveRequest: Boolean(activeRequest),
|
|
4931
5264
|
agentBusy,
|
|
5265
|
+
studioDirectRunChainActive: isStudioDirectRunChainActive(),
|
|
5266
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
4932
5267
|
});
|
|
4933
5268
|
if (activeRequest) {
|
|
4934
5269
|
broadcast({ type: "busy", requestId, message: "A studio request is already in progress." });
|
|
@@ -4942,28 +5277,91 @@ export default function (pi: ExtensionAPI) {
|
|
|
4942
5277
|
broadcast({ type: "busy", requestId, message: "pi is currently busy. Wait for the current turn to finish." });
|
|
4943
5278
|
return false;
|
|
4944
5279
|
}
|
|
5280
|
+
return activateRequest(requestId, kind, promptDescriptor);
|
|
5281
|
+
};
|
|
4945
5282
|
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
5283
|
+
const getPromptDescriptorForActiveRequest = (request: ActiveStudioRequest | null | undefined): StudioPromptDescriptor => {
|
|
5284
|
+
return buildStudioPromptDescriptor(
|
|
5285
|
+
request?.prompt ?? null,
|
|
5286
|
+
request?.promptMode ?? "response",
|
|
5287
|
+
request?.promptTriggerKind ?? null,
|
|
5288
|
+
request?.promptSteeringCount ?? 0,
|
|
5289
|
+
request?.promptTriggerText ?? null,
|
|
5290
|
+
);
|
|
5291
|
+
};
|
|
4952
5292
|
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
5293
|
+
const startStudioDirectRunChain = (prompt: string): StudioPromptDescriptor => {
|
|
5294
|
+
const normalizedPrompt = normalizePromptText(prompt) ?? prompt.trim();
|
|
5295
|
+
studioDirectRunChain = {
|
|
5296
|
+
id: randomUUID(),
|
|
5297
|
+
basePrompt: normalizedPrompt,
|
|
5298
|
+
steeringPrompts: [],
|
|
4959
5299
|
};
|
|
4960
|
-
|
|
4961
|
-
|
|
5300
|
+
queuedStudioDirectRequests = [];
|
|
5301
|
+
pendingStudioPromptMetadata = null;
|
|
5302
|
+
return buildStudioDirectRunPromptDescriptor(normalizedPrompt);
|
|
5303
|
+
};
|
|
4962
5304
|
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
return
|
|
5305
|
+
const enqueueStudioDirectSteeringRequest = (requestId: string, prompt: string): QueuedStudioDirectRequest | null => {
|
|
5306
|
+
if (!studioDirectRunChain) return null;
|
|
5307
|
+
const normalizedPrompt = normalizePromptText(prompt);
|
|
5308
|
+
if (!normalizedPrompt) return null;
|
|
5309
|
+
const descriptor = buildStudioQueuedSteerPromptDescriptor(studioDirectRunChain, normalizedPrompt);
|
|
5310
|
+
studioDirectRunChain.steeringPrompts.push(normalizedPrompt);
|
|
5311
|
+
const queuedRequest: QueuedStudioDirectRequest = {
|
|
5312
|
+
requestId,
|
|
5313
|
+
queuedAt: Date.now(),
|
|
5314
|
+
prompt: descriptor.prompt,
|
|
5315
|
+
promptMode: descriptor.promptMode,
|
|
5316
|
+
promptTriggerKind: descriptor.promptTriggerKind,
|
|
5317
|
+
promptSteeringCount: descriptor.promptSteeringCount,
|
|
5318
|
+
promptTriggerText: descriptor.promptTriggerText,
|
|
5319
|
+
};
|
|
5320
|
+
queuedStudioDirectRequests.push(queuedRequest);
|
|
5321
|
+
return queuedRequest;
|
|
5322
|
+
};
|
|
5323
|
+
|
|
5324
|
+
const claimQueuedStudioDirectRequestForPrompt = (_prompt: string | null): QueuedStudioDirectRequest | null => {
|
|
5325
|
+
if (queuedStudioDirectRequests.length === 0) return null;
|
|
5326
|
+
return queuedStudioDirectRequests.shift() ?? null;
|
|
5327
|
+
};
|
|
5328
|
+
|
|
5329
|
+
const activateQueuedStudioDirectRequestForPrompt = (prompt: string | null): QueuedStudioDirectRequest | null => {
|
|
5330
|
+
if (activeRequest) return null;
|
|
5331
|
+
const queuedRequest = claimQueuedStudioDirectRequestForPrompt(prompt);
|
|
5332
|
+
if (!queuedRequest) return null;
|
|
5333
|
+
activateRequest(queuedRequest.requestId, "direct", queuedRequest, { skipNotificationCleanup: true });
|
|
5334
|
+
return queuedRequest;
|
|
5335
|
+
};
|
|
5336
|
+
|
|
5337
|
+
const stageStudioPromptMetadata = (promptDescriptor: StudioPromptDescriptor | null | undefined) => {
|
|
5338
|
+
const descriptor = promptDescriptor ? buildStudioPromptDescriptor(
|
|
5339
|
+
promptDescriptor.prompt,
|
|
5340
|
+
promptDescriptor.promptMode,
|
|
5341
|
+
promptDescriptor.promptTriggerKind,
|
|
5342
|
+
promptDescriptor.promptSteeringCount,
|
|
5343
|
+
promptDescriptor.promptTriggerText,
|
|
5344
|
+
) : null;
|
|
5345
|
+
pendingStudioPromptMetadata = descriptor && descriptor.prompt ? descriptor : null;
|
|
5346
|
+
};
|
|
5347
|
+
|
|
5348
|
+
const persistPendingStudioPromptMetadata = () => {
|
|
5349
|
+
if (!pendingStudioPromptMetadata) return;
|
|
5350
|
+
const metadata = buildPersistedStudioPromptMetadata(pendingStudioPromptMetadata);
|
|
5351
|
+
try {
|
|
5352
|
+
pi.appendEntry(STUDIO_PROMPT_METADATA_CUSTOM_TYPE, metadata);
|
|
5353
|
+
emitDebugEvent("persist_prompt_metadata", {
|
|
5354
|
+
promptMode: metadata.promptMode,
|
|
5355
|
+
promptTriggerKind: metadata.promptTriggerKind,
|
|
5356
|
+
promptSteeringCount: metadata.promptSteeringCount,
|
|
5357
|
+
});
|
|
5358
|
+
} catch (error) {
|
|
5359
|
+
emitDebugEvent("persist_prompt_metadata_error", {
|
|
5360
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5361
|
+
});
|
|
5362
|
+
} finally {
|
|
5363
|
+
pendingStudioPromptMetadata = null;
|
|
5364
|
+
}
|
|
4967
5365
|
};
|
|
4968
5366
|
|
|
4969
5367
|
const closeAllClients = (code = 4001, reason = "Session invalidated") => {
|
|
@@ -5011,6 +5409,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
5011
5409
|
compactInProgress,
|
|
5012
5410
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
5013
5411
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
5412
|
+
studioRunChainActive: isStudioDirectRunChainActive(),
|
|
5413
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
5014
5414
|
lastResponse: lastStudioResponse,
|
|
5015
5415
|
responseHistory: studioResponseHistory,
|
|
5016
5416
|
initialDocument: initialStudioDocument,
|
|
@@ -5107,7 +5507,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5107
5507
|
|
|
5108
5508
|
const lens = resolveLens(msg.lens, document);
|
|
5109
5509
|
const prompt = buildCritiquePrompt(document, lens);
|
|
5110
|
-
if (!beginRequest(msg.requestId, "critique", prompt)) return;
|
|
5510
|
+
if (!beginRequest(msg.requestId, "critique", buildStudioPromptDescriptor(prompt))) return;
|
|
5111
5511
|
|
|
5112
5512
|
try {
|
|
5113
5513
|
pi.sendUserMessage(prompt);
|
|
@@ -5134,7 +5534,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5134
5534
|
return;
|
|
5135
5535
|
}
|
|
5136
5536
|
|
|
5137
|
-
if (!beginRequest(msg.requestId, "annotation", text)) return;
|
|
5537
|
+
if (!beginRequest(msg.requestId, "annotation", buildStudioPromptDescriptor(text))) return;
|
|
5138
5538
|
|
|
5139
5539
|
try {
|
|
5140
5540
|
pi.sendUserMessage(text);
|
|
@@ -5161,11 +5561,53 @@ export default function (pi: ExtensionAPI) {
|
|
|
5161
5561
|
return;
|
|
5162
5562
|
}
|
|
5163
5563
|
|
|
5164
|
-
if (
|
|
5564
|
+
if (canQueueStudioSteeringRequest()) {
|
|
5565
|
+
const queuedRequest = enqueueStudioDirectSteeringRequest(msg.requestId, msg.text);
|
|
5566
|
+
if (!queuedRequest) {
|
|
5567
|
+
sendToClient(client, {
|
|
5568
|
+
type: "error",
|
|
5569
|
+
requestId: msg.requestId,
|
|
5570
|
+
message: "Could not queue steering for the current run.",
|
|
5571
|
+
});
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
|
|
5575
|
+
try {
|
|
5576
|
+
pi.sendUserMessage(msg.text, { deliverAs: "steer" });
|
|
5577
|
+
broadcast({
|
|
5578
|
+
type: "request_queued",
|
|
5579
|
+
requestId: msg.requestId,
|
|
5580
|
+
kind: "direct",
|
|
5581
|
+
queueKind: "steer",
|
|
5582
|
+
studioRunChainActive: isStudioDirectRunChainActive(),
|
|
5583
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
5584
|
+
});
|
|
5585
|
+
broadcastState();
|
|
5586
|
+
} catch (error) {
|
|
5587
|
+
queuedStudioDirectRequests = queuedStudioDirectRequests.filter((request) => request.requestId !== msg.requestId);
|
|
5588
|
+
if (studioDirectRunChain?.steeringPrompts.length) {
|
|
5589
|
+
studioDirectRunChain.steeringPrompts.pop();
|
|
5590
|
+
}
|
|
5591
|
+
sendToClient(client, {
|
|
5592
|
+
type: "error",
|
|
5593
|
+
requestId: msg.requestId,
|
|
5594
|
+
message: `Failed to queue steering request: ${error instanceof Error ? error.message : String(error)}`,
|
|
5595
|
+
});
|
|
5596
|
+
broadcastState();
|
|
5597
|
+
}
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
5600
|
+
|
|
5601
|
+
const promptDescriptor = startStudioDirectRunChain(msg.text);
|
|
5602
|
+
if (!beginRequest(msg.requestId, "direct", promptDescriptor)) {
|
|
5603
|
+
clearStudioDirectRunState();
|
|
5604
|
+
return;
|
|
5605
|
+
}
|
|
5165
5606
|
|
|
5166
5607
|
try {
|
|
5167
5608
|
pi.sendUserMessage(msg.text);
|
|
5168
5609
|
} catch (error) {
|
|
5610
|
+
clearStudioDirectRunState();
|
|
5169
5611
|
clearActiveRequest();
|
|
5170
5612
|
sendToClient(client, {
|
|
5171
5613
|
type: "error",
|
|
@@ -5952,6 +6394,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5952
6394
|
|
|
5953
6395
|
const stopServer = async () => {
|
|
5954
6396
|
if (!serverState) return;
|
|
6397
|
+
clearStudioDirectRunState();
|
|
5955
6398
|
clearActiveRequest();
|
|
5956
6399
|
clearPendingStudioCompletion();
|
|
5957
6400
|
clearPreparedPdfExports();
|
|
@@ -5984,6 +6427,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5984
6427
|
|
|
5985
6428
|
pi.on("session_start", async (_event, ctx) => {
|
|
5986
6429
|
pendingTurnPrompt = null;
|
|
6430
|
+
clearStudioDirectRunState();
|
|
5987
6431
|
hydrateLatestAssistant(ctx.sessionManager.getBranch());
|
|
5988
6432
|
clearCompactionState();
|
|
5989
6433
|
agentBusy = false;
|
|
@@ -6001,6 +6445,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6001
6445
|
});
|
|
6002
6446
|
|
|
6003
6447
|
pi.on("session_switch", async (_event, ctx) => {
|
|
6448
|
+
clearStudioDirectRunState();
|
|
6004
6449
|
clearActiveRequest({ notify: "Session switched. Studio request state cleared.", level: "warning" });
|
|
6005
6450
|
clearCompactionState();
|
|
6006
6451
|
pendingTurnPrompt = null;
|
|
@@ -6071,6 +6516,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
6071
6516
|
pi.on("message_start", async (event) => {
|
|
6072
6517
|
const role = (event.message as { role?: string } | undefined)?.role;
|
|
6073
6518
|
emitDebugEvent("message_start", { role: role ?? "", activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
6519
|
+
if (role === "assistant") {
|
|
6520
|
+
persistPendingStudioPromptMetadata();
|
|
6521
|
+
}
|
|
6074
6522
|
if (agentBusy && role === "assistant") {
|
|
6075
6523
|
setTerminalActivity("responding");
|
|
6076
6524
|
}
|
|
@@ -6094,7 +6542,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
6094
6542
|
});
|
|
6095
6543
|
|
|
6096
6544
|
if (role === "user") {
|
|
6097
|
-
|
|
6545
|
+
const userPrompt = extractUserText(event.message);
|
|
6546
|
+
pendingTurnPrompt = userPrompt;
|
|
6547
|
+
const activatedQueuedRequest = activateQueuedStudioDirectRequestForPrompt(userPrompt);
|
|
6548
|
+
if (activatedQueuedRequest) {
|
|
6549
|
+
emitDebugEvent("activate_queued_request", {
|
|
6550
|
+
requestId: activatedQueuedRequest.requestId,
|
|
6551
|
+
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
6552
|
+
promptSteeringCount: activatedQueuedRequest.promptSteeringCount,
|
|
6553
|
+
});
|
|
6554
|
+
}
|
|
6555
|
+
if (activeRequest?.kind === "direct") {
|
|
6556
|
+
stageStudioPromptMetadata(getPromptDescriptorForActiveRequest(activeRequest));
|
|
6557
|
+
} else {
|
|
6558
|
+
pendingStudioPromptMetadata = null;
|
|
6559
|
+
}
|
|
6098
6560
|
return;
|
|
6099
6561
|
}
|
|
6100
6562
|
|
|
@@ -6125,14 +6587,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
6125
6587
|
refreshContextUsage(ctx);
|
|
6126
6588
|
const latestHistoryItem = studioResponseHistory[studioResponseHistory.length - 1];
|
|
6127
6589
|
if (!latestHistoryItem || latestHistoryItem.markdown !== markdown) {
|
|
6128
|
-
const
|
|
6590
|
+
const fallbackPromptDescriptor = activeRequest
|
|
6591
|
+
? getPromptDescriptorForActiveRequest(activeRequest)
|
|
6592
|
+
: buildStudioPromptDescriptor(pendingTurnPrompt ?? latestSessionUserPrompt ?? null);
|
|
6129
6593
|
const fallbackHistoryItem: StudioResponseHistoryItem = {
|
|
6130
6594
|
id: randomUUID(),
|
|
6131
6595
|
markdown,
|
|
6132
6596
|
thinking,
|
|
6133
6597
|
timestamp: Date.now(),
|
|
6134
6598
|
kind: inferStudioResponseKind(markdown),
|
|
6135
|
-
prompt:
|
|
6599
|
+
prompt: fallbackPromptDescriptor.prompt,
|
|
6600
|
+
promptMode: fallbackPromptDescriptor.promptMode,
|
|
6601
|
+
promptTriggerKind: fallbackPromptDescriptor.promptTriggerKind,
|
|
6602
|
+
promptSteeringCount: fallbackPromptDescriptor.promptSteeringCount,
|
|
6603
|
+
promptTriggerText: fallbackPromptDescriptor.promptTriggerText,
|
|
6136
6604
|
};
|
|
6137
6605
|
const nextHistory = [...studioResponseHistory, fallbackHistoryItem];
|
|
6138
6606
|
studioResponseHistory = nextHistory.slice(-RESPONSE_HISTORY_LIMIT);
|
|
@@ -6201,6 +6669,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
6201
6669
|
pi.on("agent_end", async () => {
|
|
6202
6670
|
agentBusy = false;
|
|
6203
6671
|
pendingTurnPrompt = null;
|
|
6672
|
+
pendingStudioPromptMetadata = null;
|
|
6673
|
+
const hadStudioDirectRunChain = isStudioDirectRunChainActive();
|
|
6674
|
+
const queuedSteeringCount = getQueuedStudioSteeringCount();
|
|
6204
6675
|
refreshContextUsage();
|
|
6205
6676
|
emitDebugEvent("agent_end", {
|
|
6206
6677
|
activeRequestId: activeRequest?.id ?? null,
|
|
@@ -6208,7 +6679,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
6208
6679
|
suppressedRequestId: suppressedStudioResponse?.requestId ?? null,
|
|
6209
6680
|
suppressedRequestKind: suppressedStudioResponse?.kind ?? null,
|
|
6210
6681
|
pendingCompletionKind: pendingStudioCompletionKind,
|
|
6682
|
+
hadStudioDirectRunChain,
|
|
6683
|
+
queuedSteeringCount,
|
|
6211
6684
|
});
|
|
6685
|
+
clearStudioDirectRunState();
|
|
6212
6686
|
setTerminalActivity("idle");
|
|
6213
6687
|
if (activeRequest) {
|
|
6214
6688
|
const requestId = activeRequest.id;
|
|
@@ -6221,6 +6695,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6221
6695
|
clearPendingStudioCompletion();
|
|
6222
6696
|
} else {
|
|
6223
6697
|
flushPendingStudioCompletionNotification();
|
|
6698
|
+
broadcastState();
|
|
6224
6699
|
}
|
|
6225
6700
|
suppressedStudioResponse = null;
|
|
6226
6701
|
});
|
|
@@ -6228,6 +6703,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6228
6703
|
pi.on("session_shutdown", async () => {
|
|
6229
6704
|
lastCommandCtx = null;
|
|
6230
6705
|
agentBusy = false;
|
|
6706
|
+
clearStudioDirectRunState();
|
|
6231
6707
|
clearPendingStudioCompletion();
|
|
6232
6708
|
clearPreparedPdfExports();
|
|
6233
6709
|
clearCompactionState();
|