pi-studio 0.5.52 → 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 +15 -0
- package/README.md +3 -3
- package/client/studio-client.js +513 -85
- package/client/studio.css +176 -0
- package/index.ts +332 -3
- package/package.json +1 -1
- package/shared/studio-markdown-latex-literals.js +203 -0
package/client/studio.css
CHANGED
|
@@ -1602,6 +1602,182 @@
|
|
|
1602
1602
|
flex: 0 0 auto;
|
|
1603
1603
|
}
|
|
1604
1604
|
|
|
1605
|
+
.trace-panel {
|
|
1606
|
+
display: flex;
|
|
1607
|
+
flex-direction: column;
|
|
1608
|
+
gap: 10px;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.trace-toolbar {
|
|
1612
|
+
display: flex;
|
|
1613
|
+
flex-wrap: wrap;
|
|
1614
|
+
align-items: flex-start;
|
|
1615
|
+
justify-content: space-between;
|
|
1616
|
+
gap: 10px;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
.trace-summary {
|
|
1620
|
+
display: flex;
|
|
1621
|
+
flex-wrap: wrap;
|
|
1622
|
+
align-items: center;
|
|
1623
|
+
gap: 8px;
|
|
1624
|
+
min-width: 0;
|
|
1625
|
+
padding-bottom: 2px;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.trace-controls {
|
|
1629
|
+
display: flex;
|
|
1630
|
+
flex-wrap: wrap;
|
|
1631
|
+
align-items: center;
|
|
1632
|
+
justify-content: flex-end;
|
|
1633
|
+
gap: 8px;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.trace-filter-group {
|
|
1637
|
+
display: inline-flex;
|
|
1638
|
+
align-items: center;
|
|
1639
|
+
gap: 6px;
|
|
1640
|
+
flex-wrap: wrap;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.trace-filter-btn,
|
|
1644
|
+
.trace-action-btn {
|
|
1645
|
+
padding: 0.34rem 0.68rem;
|
|
1646
|
+
font-size: 12px;
|
|
1647
|
+
border-radius: 999px;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.trace-filter-btn.is-active {
|
|
1651
|
+
border-color: var(--accent-soft-strong);
|
|
1652
|
+
background: var(--accent-soft);
|
|
1653
|
+
color: var(--accent);
|
|
1654
|
+
font-weight: 600;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.trace-summary-badge,
|
|
1658
|
+
.trace-summary-status,
|
|
1659
|
+
.trace-entry-status,
|
|
1660
|
+
.trace-kind-badge {
|
|
1661
|
+
display: inline-flex;
|
|
1662
|
+
align-items: center;
|
|
1663
|
+
border-radius: 999px;
|
|
1664
|
+
padding: 0.16rem 0.52rem;
|
|
1665
|
+
font-size: 11px;
|
|
1666
|
+
font-weight: 600;
|
|
1667
|
+
letter-spacing: 0.01em;
|
|
1668
|
+
border: 1px solid var(--border);
|
|
1669
|
+
background: transparent;
|
|
1670
|
+
color: var(--muted);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.trace-summary-badge {
|
|
1674
|
+
color: var(--text);
|
|
1675
|
+
background: var(--panel);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
.trace-kind-badge {
|
|
1679
|
+
color: var(--muted);
|
|
1680
|
+
background: var(--panel);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
.trace-summary-status.trace-status-running {
|
|
1684
|
+
color: var(--muted);
|
|
1685
|
+
border-color: var(--border);
|
|
1686
|
+
background: var(--panel);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
.trace-entry-status.trace-entry-status-streaming,
|
|
1690
|
+
.trace-entry-status.trace-entry-status-pending {
|
|
1691
|
+
color: var(--accent);
|
|
1692
|
+
border-color: var(--accent-soft-strong);
|
|
1693
|
+
background: var(--accent-soft);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.trace-summary-status.trace-status-complete,
|
|
1697
|
+
.trace-entry-status.trace-entry-status-complete {
|
|
1698
|
+
color: var(--ok);
|
|
1699
|
+
border-color: var(--ok-border);
|
|
1700
|
+
background: rgba(115, 209, 61, 0.08);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.trace-entry-status.trace-entry-status-error {
|
|
1704
|
+
color: var(--error);
|
|
1705
|
+
border-color: var(--error);
|
|
1706
|
+
background: rgba(255, 107, 107, 0.08);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
.trace-summary-meta,
|
|
1710
|
+
.trace-card-meta {
|
|
1711
|
+
color: var(--muted);
|
|
1712
|
+
font-size: 12px;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.trace-list {
|
|
1716
|
+
display: flex;
|
|
1717
|
+
flex-direction: column;
|
|
1718
|
+
gap: 10px;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
.trace-card {
|
|
1722
|
+
display: flex;
|
|
1723
|
+
flex-direction: column;
|
|
1724
|
+
gap: 8px;
|
|
1725
|
+
padding: 10px 12px;
|
|
1726
|
+
border: 1px solid var(--border-muted);
|
|
1727
|
+
border-radius: 10px;
|
|
1728
|
+
background: var(--panel-2);
|
|
1729
|
+
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.trace-card-header {
|
|
1733
|
+
display: flex;
|
|
1734
|
+
flex-wrap: wrap;
|
|
1735
|
+
align-items: center;
|
|
1736
|
+
gap: 8px;
|
|
1737
|
+
min-width: 0;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.trace-card-title {
|
|
1741
|
+
min-width: 0;
|
|
1742
|
+
font-weight: 600;
|
|
1743
|
+
color: var(--text);
|
|
1744
|
+
overflow-wrap: anywhere;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
.trace-section {
|
|
1748
|
+
display: flex;
|
|
1749
|
+
flex-direction: column;
|
|
1750
|
+
gap: 4px;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
.trace-section-label {
|
|
1754
|
+
font-size: 11px;
|
|
1755
|
+
font-weight: 600;
|
|
1756
|
+
color: var(--muted);
|
|
1757
|
+
text-transform: uppercase;
|
|
1758
|
+
letter-spacing: 0.04em;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.trace-output {
|
|
1762
|
+
padding: 10px 11px;
|
|
1763
|
+
border-radius: 8px;
|
|
1764
|
+
border: 1px solid var(--border-muted);
|
|
1765
|
+
background: var(--panel);
|
|
1766
|
+
overflow-x: auto;
|
|
1767
|
+
white-space: pre-wrap;
|
|
1768
|
+
overflow-wrap: anywhere;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
.trace-empty,
|
|
1772
|
+
.trace-empty-inline {
|
|
1773
|
+
color: var(--muted);
|
|
1774
|
+
font-style: italic;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
.trace-empty {
|
|
1778
|
+
padding: 14px 2px 2px;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1605
1781
|
footer {
|
|
1606
1782
|
border-top: 1px solid var(--border-muted);
|
|
1607
1783
|
padding: 8px 12px;
|
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,
|
|
@@ -6158,7 +6265,7 @@ ${cssVarsBlock}
|
|
|
6158
6265
|
<option value="markdown">Response (Raw)</option>
|
|
6159
6266
|
<option value="preview" selected>Response (Preview)</option>
|
|
6160
6267
|
<option value="editor-preview">Editor (Preview)</option>
|
|
6161
|
-
<option value="
|
|
6268
|
+
<option value="trace">Working</option>
|
|
6162
6269
|
</select>
|
|
6163
6270
|
</div>
|
|
6164
6271
|
<div class="section-header-actions">
|
|
@@ -6265,6 +6372,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
6265
6372
|
let studioResponseHistory: StudioResponseHistoryItem[] = [];
|
|
6266
6373
|
let latestSessionUserPrompt: string | null = null;
|
|
6267
6374
|
let pendingTurnPrompt: string | null = null;
|
|
6375
|
+
let studioTraceState: StudioTraceState = createEmptyStudioTraceState();
|
|
6376
|
+
let activeStudioTraceAssistantEntryId: string | null = null;
|
|
6377
|
+
const studioTraceToolEntryIds = new Map<string, string>();
|
|
6268
6378
|
let contextUsageSnapshot: StudioContextUsageSnapshot = {
|
|
6269
6379
|
tokens: null,
|
|
6270
6380
|
contextWindow: null,
|
|
@@ -6709,6 +6819,184 @@ export default function (pi: ExtensionAPI) {
|
|
|
6709
6819
|
});
|
|
6710
6820
|
};
|
|
6711
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
|
+
|
|
6712
7000
|
const setTerminalActivity = (phase: TerminalActivityPhase, toolName?: string | null, label?: string | null) => {
|
|
6713
7001
|
const nextPhase: TerminalActivityPhase =
|
|
6714
7002
|
phase === "running" || phase === "tool" || phase === "responding"
|
|
@@ -7070,6 +7358,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7070
7358
|
queuedSteeringCount: getQueuedStudioSteeringCount(),
|
|
7071
7359
|
lastResponse: lastStudioResponse,
|
|
7072
7360
|
responseHistory: studioResponseHistory,
|
|
7361
|
+
traceState: studioTraceState,
|
|
7073
7362
|
initialDocument: initialStudioDocument,
|
|
7074
7363
|
});
|
|
7075
7364
|
return;
|
|
@@ -8362,6 +8651,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8362
8651
|
|
|
8363
8652
|
pi.on("agent_start", async () => {
|
|
8364
8653
|
agentBusy = true;
|
|
8654
|
+
resetStudioTraceForRun();
|
|
8365
8655
|
emitDebugEvent("agent_start", { activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8366
8656
|
setTerminalActivity("running");
|
|
8367
8657
|
});
|
|
@@ -8378,12 +8668,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
8378
8668
|
pi.on("tool_execution_start", async (event) => {
|
|
8379
8669
|
if (!agentBusy) return;
|
|
8380
8670
|
const label = deriveToolActivityLabel(event.toolName, event.args);
|
|
8671
|
+
ensureStudioTraceToolEntry(event.toolCallId, event.toolName, event.args);
|
|
8381
8672
|
emitDebugEvent("tool_execution_start", { toolName: event.toolName, label, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8382
8673
|
setTerminalActivity("tool", event.toolName, label);
|
|
8383
8674
|
});
|
|
8384
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
|
+
|
|
8385
8688
|
pi.on("tool_execution_end", async (event) => {
|
|
8386
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
|
+
);
|
|
8387
8698
|
emitDebugEvent("tool_execution_end", { toolName: event.toolName, activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8388
8699
|
// Keep tool phase visible until the next tool call, assistant response phase,
|
|
8389
8700
|
// or agent_end. This avoids tool labels flashing too quickly to read.
|
|
@@ -8394,12 +8705,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
8394
8705
|
emitDebugEvent("message_start", { role: role ?? "", activeRequestId: activeRequest?.id ?? null, activeRequestKind: activeRequest?.kind ?? null });
|
|
8395
8706
|
if (role === "assistant") {
|
|
8396
8707
|
persistPendingStudioPromptMetadata();
|
|
8708
|
+
ensureStudioTraceAssistantEntry();
|
|
8397
8709
|
}
|
|
8398
8710
|
if (agentBusy && role === "assistant") {
|
|
8399
8711
|
setTerminalActivity("responding");
|
|
8400
8712
|
}
|
|
8401
8713
|
});
|
|
8402
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
|
+
|
|
8403
8728
|
pi.on("message_end", async (event, ctx) => {
|
|
8404
8729
|
const message = event.message as { stopReason?: string; role?: string };
|
|
8405
8730
|
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
|
|
@@ -8438,6 +8763,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8438
8763
|
|
|
8439
8764
|
// Assistant is handing off to tool calls; request is still in progress.
|
|
8440
8765
|
if (stopReason === "toolUse") {
|
|
8766
|
+
finalizeStudioTraceAssistantEntry(markdown, thinking, stopReason);
|
|
8441
8767
|
emitDebugEvent("message_end_tool_use", {
|
|
8442
8768
|
role,
|
|
8443
8769
|
activeRequestId: activeRequest?.id ?? null,
|
|
@@ -8446,6 +8772,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8446
8772
|
return;
|
|
8447
8773
|
}
|
|
8448
8774
|
|
|
8775
|
+
finalizeStudioTraceAssistantEntry(markdown, thinking, stopReason);
|
|
8449
8776
|
if (!markdown) return;
|
|
8450
8777
|
|
|
8451
8778
|
if (suppressedStudioResponse) {
|
|
@@ -8560,6 +8887,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8560
8887
|
});
|
|
8561
8888
|
clearStudioDirectRunState();
|
|
8562
8889
|
setTerminalActivity("idle");
|
|
8890
|
+
setStudioTraceRunStatus("complete");
|
|
8563
8891
|
if (activeRequest) {
|
|
8564
8892
|
const requestId = activeRequest.id;
|
|
8565
8893
|
broadcast({
|
|
@@ -8583,6 +8911,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8583
8911
|
clearPendingStudioCompletion();
|
|
8584
8912
|
clearPreparedPdfExports();
|
|
8585
8913
|
clearCompactionState();
|
|
8914
|
+
clearStudioTrace();
|
|
8586
8915
|
setTerminalActivity("idle");
|
|
8587
8916
|
await stopServer();
|
|
8588
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",
|