pi-studio 0.5.51 → 0.5.53
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 +27 -0
- package/README.md +4 -3
- package/client/studio-client.js +1117 -86
- package/client/studio.css +248 -9
- package/index.ts +354 -3
- package/package.json +1 -1
- package/shared/studio-markdown-latex-literals.js +203 -0
package/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
transformStudioMarkdownOutsideFences,
|
|
21
21
|
} from "./shared/studio-annotation-scanner.js";
|
|
22
22
|
import { stripStudioMarkdownHtmlComments } from "./shared/studio-markdown-html-comments.js";
|
|
23
|
+
import { preserveLiteralLatexCommandsInMarkdown } from "./shared/studio-markdown-latex-literals.js";
|
|
23
24
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
24
25
|
|
|
25
26
|
type Lens = "writing" | "code";
|
|
@@ -132,6 +133,46 @@ interface StudioPersistentState {
|
|
|
132
133
|
reviewNotesByDocument: Record<string, PersistedStudioReviewNote[]>;
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
type StudioTraceRunStatus = "idle" | "running" | "complete";
|
|
137
|
+
type StudioTraceEntryStatus = "streaming" | "pending" | "complete" | "error";
|
|
138
|
+
|
|
139
|
+
interface StudioTraceAssistantEntry {
|
|
140
|
+
id: string;
|
|
141
|
+
type: "assistant";
|
|
142
|
+
startedAt: number;
|
|
143
|
+
updatedAt: number;
|
|
144
|
+
thinking: string;
|
|
145
|
+
text: string;
|
|
146
|
+
status: StudioTraceEntryStatus;
|
|
147
|
+
stopReason: string | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface StudioTraceToolEntry {
|
|
151
|
+
id: string;
|
|
152
|
+
type: "tool";
|
|
153
|
+
toolCallId: string;
|
|
154
|
+
toolName: string;
|
|
155
|
+
label: string | null;
|
|
156
|
+
argsSummary: string | null;
|
|
157
|
+
output: string;
|
|
158
|
+
startedAt: number;
|
|
159
|
+
updatedAt: number;
|
|
160
|
+
status: StudioTraceEntryStatus;
|
|
161
|
+
isError: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type StudioTraceEntry = StudioTraceAssistantEntry | StudioTraceToolEntry;
|
|
165
|
+
|
|
166
|
+
interface StudioTraceState {
|
|
167
|
+
runId: string | null;
|
|
168
|
+
requestId: string | null;
|
|
169
|
+
requestKind: string | null;
|
|
170
|
+
status: StudioTraceRunStatus;
|
|
171
|
+
startedAt: number | null;
|
|
172
|
+
updatedAt: number | null;
|
|
173
|
+
entries: StudioTraceEntry[];
|
|
174
|
+
}
|
|
175
|
+
|
|
135
176
|
interface HelloMessage {
|
|
136
177
|
type: "hello";
|
|
137
178
|
}
|
|
@@ -3953,7 +3994,7 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
3953
3994
|
? replaceStudioAnnotationMarkersForPdf(source)
|
|
3954
3995
|
: source;
|
|
3955
3996
|
const commentStrippedSource = stripStudioMarkdownHtmlComments(annotationReadySource);
|
|
3956
|
-
return normalizeObsidianImages(normalizeMathDelimiters(commentStrippedSource));
|
|
3997
|
+
return normalizeObsidianImages(preserveLiteralLatexCommandsInMarkdown(normalizeMathDelimiters(commentStrippedSource)));
|
|
3957
3998
|
}
|
|
3958
3999
|
|
|
3959
4000
|
function stripMathMlAnnotationTags(html: string): string {
|
|
@@ -4145,7 +4186,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
4145
4186
|
}
|
|
4146
4187
|
const normalizedMarkdown = isLatex
|
|
4147
4188
|
? sourceWithResolvedRefs
|
|
4148
|
-
: normalizeStudioMarkdownFencedBlocks(normalizeObsidianImages(normalizeMathDelimiters(sourceWithResolvedRefs)));
|
|
4189
|
+
: normalizeStudioMarkdownFencedBlocks(normalizeObsidianImages(preserveLiteralLatexCommandsInMarkdown(normalizeMathDelimiters(sourceWithResolvedRefs))));
|
|
4149
4190
|
const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath);
|
|
4150
4191
|
|
|
4151
4192
|
let renderedHtml = await new Promise<string>((resolve, reject) => {
|
|
@@ -5709,6 +5750,72 @@ function deriveToolActivityLabel(toolName: string, args: unknown): string | null
|
|
|
5709
5750
|
return normalizeActivityLabel(`Running ${normalizedTool || "tool"}`);
|
|
5710
5751
|
}
|
|
5711
5752
|
|
|
5753
|
+
function createEmptyStudioTraceState(): StudioTraceState {
|
|
5754
|
+
return {
|
|
5755
|
+
runId: null,
|
|
5756
|
+
requestId: null,
|
|
5757
|
+
requestKind: null,
|
|
5758
|
+
status: "idle",
|
|
5759
|
+
startedAt: null,
|
|
5760
|
+
updatedAt: null,
|
|
5761
|
+
entries: [],
|
|
5762
|
+
};
|
|
5763
|
+
}
|
|
5764
|
+
|
|
5765
|
+
function formatStudioTraceOutput(result: unknown): string {
|
|
5766
|
+
if (result == null) return "";
|
|
5767
|
+
if (typeof result === "string") return result;
|
|
5768
|
+
if (Array.isArray(result)) {
|
|
5769
|
+
return result.map((item) => formatStudioTraceOutput(item)).filter(Boolean).join("\n");
|
|
5770
|
+
}
|
|
5771
|
+
if (typeof result === "object") {
|
|
5772
|
+
const payload = result as { content?: Array<{ type?: string; text?: string }> };
|
|
5773
|
+
if (Array.isArray(payload.content)) {
|
|
5774
|
+
return payload.content
|
|
5775
|
+
.map((block) => {
|
|
5776
|
+
if (block && block.type === "text" && typeof block.text === "string") return block.text;
|
|
5777
|
+
try {
|
|
5778
|
+
return JSON.stringify(block, null, 2);
|
|
5779
|
+
} catch {
|
|
5780
|
+
return String(block);
|
|
5781
|
+
}
|
|
5782
|
+
})
|
|
5783
|
+
.filter(Boolean)
|
|
5784
|
+
.join("\n");
|
|
5785
|
+
}
|
|
5786
|
+
try {
|
|
5787
|
+
return JSON.stringify(result, null, 2);
|
|
5788
|
+
} catch {
|
|
5789
|
+
return String(result);
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
return String(result);
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
function summarizeStudioTraceToolArgs(toolName: string, args: unknown): string | null {
|
|
5796
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
5797
|
+
const payload = (args && typeof args === "object") ? (args as Record<string, unknown>) : {};
|
|
5798
|
+
const trimSummary = (value: string | null | undefined): string | null => {
|
|
5799
|
+
const compact = normalizeActivityLabel(String(value || "").replace(/\s+/g, " ").trim());
|
|
5800
|
+
return compact && compact.length <= 220 ? compact : (compact ? `${compact.slice(0, 217).trimEnd()}…` : null);
|
|
5801
|
+
};
|
|
5802
|
+
|
|
5803
|
+
if (normalizedTool === "bash") {
|
|
5804
|
+
return trimSummary(typeof payload.command === "string" ? payload.command : "");
|
|
5805
|
+
}
|
|
5806
|
+
if (normalizedTool === "read" || normalizedTool === "write" || normalizedTool === "edit") {
|
|
5807
|
+
return trimSummary(typeof payload.path === "string" ? payload.path : "");
|
|
5808
|
+
}
|
|
5809
|
+
if (normalizedTool === "repl_send") {
|
|
5810
|
+
return trimSummary(typeof payload.code === "string" ? payload.code : "");
|
|
5811
|
+
}
|
|
5812
|
+
try {
|
|
5813
|
+
return trimSummary(JSON.stringify(args, null, 2));
|
|
5814
|
+
} catch {
|
|
5815
|
+
return trimSummary(String(args ?? ""));
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5712
5819
|
function isAllowedOrigin(_origin: string | undefined, _port: number): boolean {
|
|
5713
5820
|
// For local-only studio, token auth is the primary guard. In practice,
|
|
5714
5821
|
// browser origin headers can vary (or be omitted) across wrappers/browsers,
|
|
@@ -6010,6 +6117,7 @@ ${cssVarsBlock}
|
|
|
6010
6117
|
<div class="section-header-actions">
|
|
6011
6118
|
<button id="leftFocusBtn" class="pane-focus-btn" type="button" title="Show only the editor pane. Shortcut: F10 or Cmd/Ctrl+Esc.">Focus pane</button>
|
|
6012
6119
|
<button id="reviewNotesBtn" type="button" title="Toggle local comments beside the current editor document or draft. Comments stay outside the document text and can later be converted into [an: ...] annotations.">Comments</button>
|
|
6120
|
+
<button id="outlineBtn" type="button" title="Toggle document outline for the current editor text. Outline entries can jump between raw editor and preview.">Outline</button>
|
|
6013
6121
|
<button id="scratchpadBtn" type="button" title="Open a local persistent scratchpad for the current editor document or draft. Scratchpad text is never run, critiqued, or exported unless you explicitly insert it into the editor.">Scratchpad</button>
|
|
6014
6122
|
</div>
|
|
6015
6123
|
</div>
|
|
@@ -6102,6 +6210,27 @@ ${cssVarsBlock}
|
|
|
6102
6210
|
</div>
|
|
6103
6211
|
<div id="sourcePreview" class="panel-scroll rendered-markdown" hidden><pre class="plain-markdown"></pre></div>
|
|
6104
6212
|
</div>
|
|
6213
|
+
<aside id="outlineOverlay" class="outline-dock-wrap" hidden>
|
|
6214
|
+
<div id="outlineDialog" class="outline-dock" role="complementary" aria-labelledby="outlineTitle">
|
|
6215
|
+
<div class="scratchpad-header">
|
|
6216
|
+
<div>
|
|
6217
|
+
<h2 id="outlineTitle">Outline</h2>
|
|
6218
|
+
<p class="scratchpad-description">Document structure for the current editor text. Click an entry to jump in the raw editor and, when available, reveal the matching preview location.</p>
|
|
6219
|
+
</div>
|
|
6220
|
+
<button id="outlineCloseBtn" type="button" class="scratchpad-close-btn" aria-label="Hide outline" title="Hide outline">✕</button>
|
|
6221
|
+
</div>
|
|
6222
|
+
<div class="review-notes-toolbar">
|
|
6223
|
+
<span id="outlineMeta" class="scratchpad-meta">No outline entries</span>
|
|
6224
|
+
</div>
|
|
6225
|
+
<div id="outlineEmptyState" class="review-notes-empty">No outline available yet for this document or syntax mode.</div>
|
|
6226
|
+
<div id="outlineList" class="outline-list" aria-live="polite"></div>
|
|
6227
|
+
<div class="review-notes-dock-footer">
|
|
6228
|
+
<div class="scratchpad-actions">
|
|
6229
|
+
<button id="outlineDoneBtn" type="button" title="Hide the outline rail.">Hide</button>
|
|
6230
|
+
</div>
|
|
6231
|
+
</div>
|
|
6232
|
+
</div>
|
|
6233
|
+
</aside>
|
|
6105
6234
|
<aside id="reviewNotesOverlay" class="review-notes-dock-wrap" hidden>
|
|
6106
6235
|
<div id="reviewNotesDialog" class="review-notes-dock" role="complementary" aria-labelledby="reviewNotesTitle">
|
|
6107
6236
|
<div class="scratchpad-header">
|
|
@@ -6136,7 +6265,7 @@ ${cssVarsBlock}
|
|
|
6136
6265
|
<option value="markdown">Response (Raw)</option>
|
|
6137
6266
|
<option value="preview" selected>Response (Preview)</option>
|
|
6138
6267
|
<option value="editor-preview">Editor (Preview)</option>
|
|
6139
|
-
<option value="
|
|
6268
|
+
<option value="trace">Working</option>
|
|
6140
6269
|
</select>
|
|
6141
6270
|
</div>
|
|
6142
6271
|
<div class="section-header-actions">
|
|
@@ -6243,6 +6372,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
6243
6372
|
let studioResponseHistory: StudioResponseHistoryItem[] = [];
|
|
6244
6373
|
let latestSessionUserPrompt: string | null = null;
|
|
6245
6374
|
let pendingTurnPrompt: string | null = null;
|
|
6375
|
+
let studioTraceState: StudioTraceState = createEmptyStudioTraceState();
|
|
6376
|
+
let activeStudioTraceAssistantEntryId: string | null = null;
|
|
6377
|
+
const studioTraceToolEntryIds = new Map<string, string>();
|
|
6246
6378
|
let contextUsageSnapshot: StudioContextUsageSnapshot = {
|
|
6247
6379
|
tokens: null,
|
|
6248
6380
|
contextWindow: null,
|
|
@@ -6687,6 +6819,184 @@ export default function (pi: ExtensionAPI) {
|
|
|
6687
6819
|
});
|
|
6688
6820
|
};
|
|
6689
6821
|
|
|
6822
|
+
const broadcastStudioTraceReset = () => {
|
|
6823
|
+
broadcast({
|
|
6824
|
+
type: "trace_reset",
|
|
6825
|
+
trace: studioTraceState,
|
|
6826
|
+
});
|
|
6827
|
+
};
|
|
6828
|
+
|
|
6829
|
+
const broadcastStudioTraceStatus = () => {
|
|
6830
|
+
broadcast({
|
|
6831
|
+
type: "trace_status",
|
|
6832
|
+
runId: studioTraceState.runId,
|
|
6833
|
+
requestId: studioTraceState.requestId,
|
|
6834
|
+
requestKind: studioTraceState.requestKind,
|
|
6835
|
+
status: studioTraceState.status,
|
|
6836
|
+
startedAt: studioTraceState.startedAt,
|
|
6837
|
+
updatedAt: studioTraceState.updatedAt,
|
|
6838
|
+
});
|
|
6839
|
+
};
|
|
6840
|
+
|
|
6841
|
+
const upsertStudioTraceEntry = (entry: StudioTraceEntry) => {
|
|
6842
|
+
const entryIndex = studioTraceState.entries.findIndex((candidate) => candidate.id === entry.id);
|
|
6843
|
+
if (entryIndex >= 0) {
|
|
6844
|
+
studioTraceState.entries[entryIndex] = entry;
|
|
6845
|
+
} else {
|
|
6846
|
+
studioTraceState.entries.push(entry);
|
|
6847
|
+
}
|
|
6848
|
+
studioTraceState.updatedAt = entry.updatedAt;
|
|
6849
|
+
broadcast({
|
|
6850
|
+
type: "trace_entry_upsert",
|
|
6851
|
+
entry,
|
|
6852
|
+
runId: studioTraceState.runId,
|
|
6853
|
+
});
|
|
6854
|
+
};
|
|
6855
|
+
|
|
6856
|
+
const resetStudioTraceForRun = () => {
|
|
6857
|
+
const now = Date.now();
|
|
6858
|
+
studioTraceState = {
|
|
6859
|
+
runId: randomUUID(),
|
|
6860
|
+
requestId: activeRequest?.id ?? null,
|
|
6861
|
+
requestKind: activeRequest?.kind ?? null,
|
|
6862
|
+
status: "running",
|
|
6863
|
+
startedAt: now,
|
|
6864
|
+
updatedAt: now,
|
|
6865
|
+
entries: [],
|
|
6866
|
+
};
|
|
6867
|
+
activeStudioTraceAssistantEntryId = null;
|
|
6868
|
+
studioTraceToolEntryIds.clear();
|
|
6869
|
+
broadcastStudioTraceReset();
|
|
6870
|
+
};
|
|
6871
|
+
|
|
6872
|
+
const setStudioTraceRunStatus = (status: StudioTraceRunStatus) => {
|
|
6873
|
+
if (studioTraceState.runId == null && status !== "idle") {
|
|
6874
|
+
resetStudioTraceForRun();
|
|
6875
|
+
}
|
|
6876
|
+
studioTraceState.status = status;
|
|
6877
|
+
studioTraceState.requestId = activeRequest?.id ?? studioTraceState.requestId ?? null;
|
|
6878
|
+
studioTraceState.requestKind = activeRequest?.kind ?? studioTraceState.requestKind ?? null;
|
|
6879
|
+
studioTraceState.updatedAt = Date.now();
|
|
6880
|
+
broadcastStudioTraceStatus();
|
|
6881
|
+
};
|
|
6882
|
+
|
|
6883
|
+
const ensureStudioTraceAssistantEntry = (): StudioTraceAssistantEntry => {
|
|
6884
|
+
if (activeStudioTraceAssistantEntryId) {
|
|
6885
|
+
const existing = studioTraceState.entries.find((entry) => entry.id === activeStudioTraceAssistantEntryId);
|
|
6886
|
+
if (existing && existing.type === "assistant") return existing;
|
|
6887
|
+
}
|
|
6888
|
+
if (studioTraceState.runId == null || studioTraceState.status === "idle") {
|
|
6889
|
+
resetStudioTraceForRun();
|
|
6890
|
+
}
|
|
6891
|
+
const now = Date.now();
|
|
6892
|
+
const entry: StudioTraceAssistantEntry = {
|
|
6893
|
+
id: randomUUID(),
|
|
6894
|
+
type: "assistant",
|
|
6895
|
+
startedAt: now,
|
|
6896
|
+
updatedAt: now,
|
|
6897
|
+
thinking: "",
|
|
6898
|
+
text: "",
|
|
6899
|
+
status: "streaming",
|
|
6900
|
+
stopReason: null,
|
|
6901
|
+
};
|
|
6902
|
+
activeStudioTraceAssistantEntryId = entry.id;
|
|
6903
|
+
upsertStudioTraceEntry(entry);
|
|
6904
|
+
return entry;
|
|
6905
|
+
};
|
|
6906
|
+
|
|
6907
|
+
const appendStudioTraceAssistantDelta = (deltaKind: "thinking" | "text", delta: string) => {
|
|
6908
|
+
if (!delta) return;
|
|
6909
|
+
const entry = ensureStudioTraceAssistantEntry();
|
|
6910
|
+
if (deltaKind === "thinking") {
|
|
6911
|
+
entry.thinking += delta;
|
|
6912
|
+
} else {
|
|
6913
|
+
entry.text += delta;
|
|
6914
|
+
}
|
|
6915
|
+
entry.status = "streaming";
|
|
6916
|
+
entry.updatedAt = Date.now();
|
|
6917
|
+
studioTraceState.updatedAt = entry.updatedAt;
|
|
6918
|
+
broadcast({
|
|
6919
|
+
type: "trace_assistant_delta",
|
|
6920
|
+
entryId: entry.id,
|
|
6921
|
+
deltaKind,
|
|
6922
|
+
delta,
|
|
6923
|
+
updatedAt: entry.updatedAt,
|
|
6924
|
+
runId: studioTraceState.runId,
|
|
6925
|
+
});
|
|
6926
|
+
};
|
|
6927
|
+
|
|
6928
|
+
const finalizeStudioTraceAssistantEntry = (text: string | null, thinking: string | null, stopReason?: string | null) => {
|
|
6929
|
+
const now = Date.now();
|
|
6930
|
+
let entry = activeStudioTraceAssistantEntryId
|
|
6931
|
+
? studioTraceState.entries.find((candidate) => candidate.id === activeStudioTraceAssistantEntryId)
|
|
6932
|
+
: null;
|
|
6933
|
+
if (!entry || entry.type !== "assistant") {
|
|
6934
|
+
if (!(text && text.trim()) && !(thinking && thinking.trim())) {
|
|
6935
|
+
activeStudioTraceAssistantEntryId = null;
|
|
6936
|
+
return;
|
|
6937
|
+
}
|
|
6938
|
+
entry = ensureStudioTraceAssistantEntry();
|
|
6939
|
+
}
|
|
6940
|
+
entry.text = typeof text === "string" ? text : entry.text;
|
|
6941
|
+
entry.thinking = typeof thinking === "string" ? thinking : entry.thinking;
|
|
6942
|
+
entry.stopReason = typeof stopReason === "string" && stopReason.trim() ? stopReason : null;
|
|
6943
|
+
entry.status = "complete";
|
|
6944
|
+
entry.updatedAt = now;
|
|
6945
|
+
upsertStudioTraceEntry(entry);
|
|
6946
|
+
activeStudioTraceAssistantEntryId = null;
|
|
6947
|
+
};
|
|
6948
|
+
|
|
6949
|
+
const ensureStudioTraceToolEntry = (toolCallId: string, toolName: string, args: unknown): StudioTraceToolEntry => {
|
|
6950
|
+
const existingId = studioTraceToolEntryIds.get(toolCallId);
|
|
6951
|
+
if (existingId) {
|
|
6952
|
+
const existing = studioTraceState.entries.find((entry) => entry.id === existingId);
|
|
6953
|
+
if (existing && existing.type === "tool") return existing;
|
|
6954
|
+
}
|
|
6955
|
+
if (studioTraceState.runId == null || studioTraceState.status === "idle") {
|
|
6956
|
+
resetStudioTraceForRun();
|
|
6957
|
+
}
|
|
6958
|
+
const now = Date.now();
|
|
6959
|
+
const entry: StudioTraceToolEntry = {
|
|
6960
|
+
id: randomUUID(),
|
|
6961
|
+
type: "tool",
|
|
6962
|
+
toolCallId,
|
|
6963
|
+
toolName,
|
|
6964
|
+
label: deriveToolActivityLabel(toolName, args),
|
|
6965
|
+
argsSummary: summarizeStudioTraceToolArgs(toolName, args),
|
|
6966
|
+
output: "",
|
|
6967
|
+
startedAt: now,
|
|
6968
|
+
updatedAt: now,
|
|
6969
|
+
status: "pending",
|
|
6970
|
+
isError: false,
|
|
6971
|
+
};
|
|
6972
|
+
studioTraceToolEntryIds.set(toolCallId, entry.id);
|
|
6973
|
+
upsertStudioTraceEntry(entry);
|
|
6974
|
+
return entry;
|
|
6975
|
+
};
|
|
6976
|
+
|
|
6977
|
+
const updateStudioTraceToolEntry = (
|
|
6978
|
+
toolCallId: string,
|
|
6979
|
+
toolName: string,
|
|
6980
|
+
args: unknown,
|
|
6981
|
+
output: string,
|
|
6982
|
+
status: StudioTraceEntryStatus,
|
|
6983
|
+
isError: boolean,
|
|
6984
|
+
) => {
|
|
6985
|
+
const entry = ensureStudioTraceToolEntry(toolCallId, toolName, args);
|
|
6986
|
+
entry.output = output;
|
|
6987
|
+
entry.status = status;
|
|
6988
|
+
entry.isError = isError;
|
|
6989
|
+
entry.updatedAt = Date.now();
|
|
6990
|
+
upsertStudioTraceEntry(entry);
|
|
6991
|
+
};
|
|
6992
|
+
|
|
6993
|
+
const clearStudioTrace = () => {
|
|
6994
|
+
studioTraceState = createEmptyStudioTraceState();
|
|
6995
|
+
activeStudioTraceAssistantEntryId = null;
|
|
6996
|
+
studioTraceToolEntryIds.clear();
|
|
6997
|
+
broadcastStudioTraceReset();
|
|
6998
|
+
};
|
|
6999
|
+
|
|
6690
7000
|
const setTerminalActivity = (phase: TerminalActivityPhase, toolName?: string | null, label?: string | null) => {
|
|
6691
7001
|
const nextPhase: TerminalActivityPhase =
|
|
6692
7002
|
phase === "running" || phase === "tool" || phase === "responding"
|
|
@@ -7048,6 +7358,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7048
7358
|
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
7049
7359
|
lastResponse: lastStudioResponse,
|
|
7050
7360
|
responseHistory: studioResponseHistory,
|
|
7361
|
+
traceState: studioTraceState,
|
|
7051
7362
|
initialDocument: initialStudioDocument,
|
|
7052
7363
|
});
|
|
7053
7364
|
return;
|
|
@@ -8340,6 +8651,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8340
8651
|
|
|
8341
8652
|
pi.on("agent_start", async () => {
|
|
8342
8653
|
agentBusy = true;
|
|
8654
|
+
resetStudioTraceForRun();
|
|
8343
8655
|
emitDebugEvent("agent_start", { activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8344
8656
|
setTerminalActivity("running");
|
|
8345
8657
|
});
|
|
@@ -8356,12 +8668,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
8356
8668
|
pi.on("tool_execution_start", async (event) => {
|
|
8357
8669
|
if (!agentBusy) return;
|
|
8358
8670
|
const label = deriveToolActivityLabel(event.toolName, event.args);
|
|
8671
|
+
ensureStudioTraceToolEntry(event.toolCallId, event.toolName, event.args);
|
|
8359
8672
|
emitDebugEvent("tool_execution_start", { toolName: event.toolName, label, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8360
8673
|
setTerminalActivity("tool", event.toolName, label);
|
|
8361
8674
|
});
|
|
8362
8675
|
|
|
8676
|
+
pi.on("tool_execution_update", async (event) => {
|
|
8677
|
+
if (!agentBusy) return;
|
|
8678
|
+
updateStudioTraceToolEntry(
|
|
8679
|
+
event.toolCallId,
|
|
8680
|
+
event.toolName,
|
|
8681
|
+
event.args,
|
|
8682
|
+
formatStudioTraceOutput(event.partialResult),
|
|
8683
|
+
"streaming",
|
|
8684
|
+
false,
|
|
8685
|
+
);
|
|
8686
|
+
});
|
|
8687
|
+
|
|
8363
8688
|
pi.on("tool_execution_end", async (event) => {
|
|
8364
8689
|
if (!agentBusy) return;
|
|
8690
|
+
updateStudioTraceToolEntry(
|
|
8691
|
+
event.toolCallId,
|
|
8692
|
+
event.toolName,
|
|
8693
|
+
undefined,
|
|
8694
|
+
formatStudioTraceOutput(event.result),
|
|
8695
|
+
event.isError ? "error" : "complete",
|
|
8696
|
+
Boolean(event.isError),
|
|
8697
|
+
);
|
|
8365
8698
|
emitDebugEvent("tool_execution_end", { toolName: event.toolName, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8366
8699
|
// Keep tool phase visible until the next tool call, assistant response phase,
|
|
8367
8700
|
// or agent_end. This avoids tool labels flashing too quickly to read.
|
|
@@ -8372,12 +8705,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
8372
8705
|
emitDebugEvent("message_start", { role: role ?? "", activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8373
8706
|
if (role === "assistant") {
|
|
8374
8707
|
persistPendingStudioPromptMetadata();
|
|
8708
|
+
ensureStudioTraceAssistantEntry();
|
|
8375
8709
|
}
|
|
8376
8710
|
if (agentBusy && role === "assistant") {
|
|
8377
8711
|
setTerminalActivity("responding");
|
|
8378
8712
|
}
|
|
8379
8713
|
});
|
|
8380
8714
|
|
|
8715
|
+
pi.on("message_update", async (event) => {
|
|
8716
|
+
if (!agentBusy) return;
|
|
8717
|
+
const deltaEvent = event.assistantMessageEvent as { type?: string; delta?: string } | undefined;
|
|
8718
|
+
if (!deltaEvent || typeof deltaEvent.delta !== "string" || !deltaEvent.delta) return;
|
|
8719
|
+
if (deltaEvent.type === "thinking_delta") {
|
|
8720
|
+
appendStudioTraceAssistantDelta("thinking", deltaEvent.delta);
|
|
8721
|
+
return;
|
|
8722
|
+
}
|
|
8723
|
+
if (deltaEvent.type === "text_delta") {
|
|
8724
|
+
appendStudioTraceAssistantDelta("text", deltaEvent.delta);
|
|
8725
|
+
}
|
|
8726
|
+
});
|
|
8727
|
+
|
|
8381
8728
|
pi.on("message_end", async (event, ctx) => {
|
|
8382
8729
|
const message = event.message as { stopReason?: string; role?: string };
|
|
8383
8730
|
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
|
|
@@ -8416,6 +8763,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8416
8763
|
|
|
8417
8764
|
// Assistant is handing off to tool calls; request is still in progress.
|
|
8418
8765
|
if (stopReason === "toolUse") {
|
|
8766
|
+
finalizeStudioTraceAssistantEntry(markdown, thinking, stopReason);
|
|
8419
8767
|
emitDebugEvent("message_end_tool_use", {
|
|
8420
8768
|
role,
|
|
8421
8769
|
activeRequestId: activeRequest?.id ?? null,
|
|
@@ -8424,6 +8772,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8424
8772
|
return;
|
|
8425
8773
|
}
|
|
8426
8774
|
|
|
8775
|
+
finalizeStudioTraceAssistantEntry(markdown, thinking, stopReason);
|
|
8427
8776
|
if (!markdown) return;
|
|
8428
8777
|
|
|
8429
8778
|
if (suppressedStudioResponse) {
|
|
@@ -8538,6 +8887,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8538
8887
|
});
|
|
8539
8888
|
clearStudioDirectRunState();
|
|
8540
8889
|
setTerminalActivity("idle");
|
|
8890
|
+
setStudioTraceRunStatus("complete");
|
|
8541
8891
|
if (activeRequest) {
|
|
8542
8892
|
const requestId = activeRequest.id;
|
|
8543
8893
|
broadcast({
|
|
@@ -8561,6 +8911,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8561
8911
|
clearPendingStudioCompletion();
|
|
8562
8912
|
clearPreparedPdfExports();
|
|
8563
8913
|
clearCompactionState();
|
|
8914
|
+
clearStudioTrace();
|
|
8564
8915
|
setTerminalActivity("idle");
|
|
8565
8916
|
await stopServer();
|
|
8566
8917
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.53",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const STUDIO_LITERAL_MARKDOWN_LATEX_COMMANDS = new Set([
|
|
2
|
+
"documentclass",
|
|
3
|
+
"usepackage",
|
|
4
|
+
"newtheorem",
|
|
5
|
+
"begin",
|
|
6
|
+
"end",
|
|
7
|
+
"section",
|
|
8
|
+
"subsection",
|
|
9
|
+
"subsubsection",
|
|
10
|
+
"chapter",
|
|
11
|
+
"part",
|
|
12
|
+
"paragraph",
|
|
13
|
+
"subparagraph",
|
|
14
|
+
"title",
|
|
15
|
+
"author",
|
|
16
|
+
"date",
|
|
17
|
+
"maketitle",
|
|
18
|
+
"tableofcontents",
|
|
19
|
+
"includegraphics",
|
|
20
|
+
"caption",
|
|
21
|
+
"label",
|
|
22
|
+
"ref",
|
|
23
|
+
"eqref",
|
|
24
|
+
"autoref",
|
|
25
|
+
"pageref",
|
|
26
|
+
"cite",
|
|
27
|
+
"citet",
|
|
28
|
+
"citep",
|
|
29
|
+
"citealt",
|
|
30
|
+
"citeauthor",
|
|
31
|
+
"nocite",
|
|
32
|
+
"textbf",
|
|
33
|
+
"textit",
|
|
34
|
+
"texttt",
|
|
35
|
+
"emph",
|
|
36
|
+
"underline",
|
|
37
|
+
"footnote",
|
|
38
|
+
"centering",
|
|
39
|
+
"newcommand",
|
|
40
|
+
"renewcommand",
|
|
41
|
+
"providecommand",
|
|
42
|
+
"bibliography",
|
|
43
|
+
"bibliographystyle",
|
|
44
|
+
"printbibliography",
|
|
45
|
+
"addbibresource",
|
|
46
|
+
"bibitem",
|
|
47
|
+
"item",
|
|
48
|
+
"input",
|
|
49
|
+
"include",
|
|
50
|
+
"latex",
|
|
51
|
+
"tex",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
function isEscapedAt(text, index) {
|
|
55
|
+
let slashCount = 0;
|
|
56
|
+
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
57
|
+
slashCount += 1;
|
|
58
|
+
}
|
|
59
|
+
return slashCount % 2 === 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findClosingUnescapedDelimiter(text, startIndex, delimiter) {
|
|
63
|
+
let searchIndex = Math.max(0, startIndex);
|
|
64
|
+
while (searchIndex <= text.length) {
|
|
65
|
+
const matchIndex = text.indexOf(delimiter, searchIndex);
|
|
66
|
+
if (matchIndex < 0) return -1;
|
|
67
|
+
if (!isEscapedAt(text, matchIndex)) return matchIndex;
|
|
68
|
+
searchIndex = matchIndex + delimiter.length;
|
|
69
|
+
}
|
|
70
|
+
return -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function preserveLiteralLatexCommandsInMarkdownSegment(markdown) {
|
|
74
|
+
const source = String(markdown || "");
|
|
75
|
+
let out = "";
|
|
76
|
+
let index = 0;
|
|
77
|
+
|
|
78
|
+
while (index < source.length) {
|
|
79
|
+
if (source[index] === "`") {
|
|
80
|
+
let tickCount = 1;
|
|
81
|
+
while (source[index + tickCount] === "`") tickCount += 1;
|
|
82
|
+
const fence = "`".repeat(tickCount);
|
|
83
|
+
const closeIndex = source.indexOf(fence, index + tickCount);
|
|
84
|
+
if (closeIndex >= 0) {
|
|
85
|
+
out += source.slice(index, closeIndex + tickCount);
|
|
86
|
+
index = closeIndex + tickCount;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (source.startsWith("$$", index) && !isEscapedAt(source, index)) {
|
|
92
|
+
const closeIndex = findClosingUnescapedDelimiter(source, index + 2, "$$");
|
|
93
|
+
if (closeIndex >= 0) {
|
|
94
|
+
out += source.slice(index, closeIndex + 2);
|
|
95
|
+
index = closeIndex + 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (source[index] === "$" && !isEscapedAt(source, index)) {
|
|
101
|
+
const closeIndex = findClosingUnescapedDelimiter(source, index + 1, "$");
|
|
102
|
+
if (closeIndex >= 0) {
|
|
103
|
+
out += source.slice(index, closeIndex + 1);
|
|
104
|
+
index = closeIndex + 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (source.startsWith("\\(", index)) {
|
|
110
|
+
const closeIndex = source.indexOf("\\)", index + 2);
|
|
111
|
+
if (closeIndex >= 0) {
|
|
112
|
+
out += source.slice(index, closeIndex + 2);
|
|
113
|
+
index = closeIndex + 2;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (source.startsWith("\\[", index)) {
|
|
119
|
+
const closeIndex = source.indexOf("\\]", index + 2);
|
|
120
|
+
if (closeIndex >= 0) {
|
|
121
|
+
out += source.slice(index, closeIndex + 2);
|
|
122
|
+
index = closeIndex + 2;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (source[index] === "\\" && source[index + 1] === "\\") {
|
|
128
|
+
out += "\\\\";
|
|
129
|
+
index += 2;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (source[index] === "\\" && /[A-Za-z@]/.test(source[index + 1] || "")) {
|
|
134
|
+
let endIndex = index + 1;
|
|
135
|
+
while (/[A-Za-z@]/.test(source[endIndex] || "")) endIndex += 1;
|
|
136
|
+
if (source[endIndex] === "*") endIndex += 1;
|
|
137
|
+
const commandName = source.slice(index + 1, endIndex).replace(/\*$/, "").toLowerCase();
|
|
138
|
+
if (STUDIO_LITERAL_MARKDOWN_LATEX_COMMANDS.has(commandName)) {
|
|
139
|
+
out += "\\" + source.slice(index, endIndex);
|
|
140
|
+
index = endIndex;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
out += source[index];
|
|
146
|
+
index += 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function preserveLiteralLatexCommandsInMarkdown(markdown) {
|
|
153
|
+
const lines = String(markdown || "").split("\n");
|
|
154
|
+
const out = [];
|
|
155
|
+
let plainBuffer = [];
|
|
156
|
+
let inFence = false;
|
|
157
|
+
let fenceChar;
|
|
158
|
+
let fenceLength = 0;
|
|
159
|
+
|
|
160
|
+
const flushPlain = () => {
|
|
161
|
+
if (plainBuffer.length === 0) return;
|
|
162
|
+
out.push(preserveLiteralLatexCommandsInMarkdownSegment(plainBuffer.join("\n")));
|
|
163
|
+
plainBuffer = [];
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
const trimmed = line.trimStart();
|
|
168
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
169
|
+
|
|
170
|
+
if (fenceMatch) {
|
|
171
|
+
const marker = fenceMatch[1];
|
|
172
|
+
const markerChar = marker[0];
|
|
173
|
+
const markerLength = marker.length;
|
|
174
|
+
|
|
175
|
+
if (!inFence) {
|
|
176
|
+
flushPlain();
|
|
177
|
+
inFence = true;
|
|
178
|
+
fenceChar = markerChar;
|
|
179
|
+
fenceLength = markerLength;
|
|
180
|
+
out.push(line);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
185
|
+
inFence = false;
|
|
186
|
+
fenceChar = undefined;
|
|
187
|
+
fenceLength = 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
out.push(line);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (inFence) {
|
|
195
|
+
out.push(line);
|
|
196
|
+
} else {
|
|
197
|
+
plainBuffer.push(line);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
flushPlain();
|
|
202
|
+
return out.join("\n");
|
|
203
|
+
}
|