pi-studio 0.5.59 → 0.6.0
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 +138 -21
- package/client/studio.css +130 -61
- package/index.ts +70 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.0] — 2026-04-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- The comments rail now includes **Comments → prompt**, which turns non-empty local comments into an editor prompt with line anchors and file labels when available.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- The refreshed Studio layout is now the default, with the classic layout still available via the footer UI switch or `?uiRefresh=0`.
|
|
14
|
+
- Working-view tool output now replaces image/base64 payloads with compact placeholders instead of dumping raw image data.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Queued steering now updates Studio's active effective-prompt metadata so response history and prompt loading reflect the original run plus steering messages.
|
|
18
|
+
- Newly arrived responses now force the right response pane to reset to the top, while editor-preview/document views still preserve scroll.
|
|
19
|
+
|
|
7
20
|
## [0.5.59] — 2026-04-27
|
|
8
21
|
|
|
9
22
|
### Added
|
package/client/studio-client.js
CHANGED
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
const reviewNotesListEl = document.getElementById("reviewNotesList");
|
|
133
133
|
const reviewNotesEmptyStateEl = document.getElementById("reviewNotesEmptyState");
|
|
134
134
|
const reviewNotesAddBtn = document.getElementById("reviewNotesAddBtn");
|
|
135
|
+
const reviewNotesPromptBtn = document.getElementById("reviewNotesPromptBtn");
|
|
135
136
|
const reviewNotesInlineAllBtn = document.getElementById("reviewNotesInlineAllBtn");
|
|
136
137
|
const reviewNotesDeleteAllBtn = document.getElementById("reviewNotesDeleteAllBtn");
|
|
137
138
|
const reviewNotesCloseBtn = document.getElementById("reviewNotesCloseBtn");
|
|
@@ -674,21 +675,21 @@
|
|
|
674
675
|
const queryValue = initialQueryParams.has("uiRefresh")
|
|
675
676
|
? initialQueryParams.get("uiRefresh")
|
|
676
677
|
: (initialQueryParams.has("studioUiRefresh") ? initialQueryParams.get("studioUiRefresh") : null);
|
|
677
|
-
const isTruthy = (value) => ["1", "true", "yes", "on", "v2", "refresh"].indexOf(normalize(value)) !== -1;
|
|
678
|
-
const isFalsey = (value) => ["0", "false", "no", "off"].indexOf(normalize(value)) !== -1;
|
|
678
|
+
const isTruthy = (value) => ["1", "true", "yes", "on", "v2", "refresh", "fresh"].indexOf(normalize(value)) !== -1;
|
|
679
|
+
const isFalsey = (value) => ["0", "false", "no", "off", "classic"].indexOf(normalize(value)) !== -1;
|
|
679
680
|
if (queryValue !== null) {
|
|
680
|
-
const
|
|
681
|
+
const normalizedQuery = normalize(queryValue);
|
|
682
|
+
const enabled = isTruthy(queryValue) || (!isFalsey(queryValue) && normalizedQuery !== "");
|
|
681
683
|
try {
|
|
682
|
-
|
|
683
|
-
else window.localStorage && window.localStorage.removeItem(STUDIO_UI_REFRESH_STORAGE_KEY);
|
|
684
|
+
window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, enabled ? "1" : "0");
|
|
684
685
|
} catch {}
|
|
685
686
|
return enabled;
|
|
686
687
|
}
|
|
687
688
|
try {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
689
|
+
const stored = window.localStorage ? window.localStorage.getItem(STUDIO_UI_REFRESH_STORAGE_KEY) : null;
|
|
690
|
+
if (stored !== null) return stored !== "0" && !isFalsey(stored);
|
|
691
|
+
} catch {}
|
|
692
|
+
return true;
|
|
692
693
|
}
|
|
693
694
|
|
|
694
695
|
function makeStudioUiRefreshElement(tagName, className, text) {
|
|
@@ -841,8 +842,7 @@
|
|
|
841
842
|
|
|
842
843
|
function setStudioUiRefreshPreference(enabled) {
|
|
843
844
|
try {
|
|
844
|
-
|
|
845
|
-
else window.localStorage && window.localStorage.removeItem(STUDIO_UI_REFRESH_STORAGE_KEY);
|
|
845
|
+
window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, enabled ? "1" : "0");
|
|
846
846
|
} catch {}
|
|
847
847
|
try {
|
|
848
848
|
const url = new URL(window.location.href);
|
|
@@ -855,7 +855,7 @@
|
|
|
855
855
|
|
|
856
856
|
function setupStudioUiRefreshToggleButton() {
|
|
857
857
|
if (!footerMetaEl || document.getElementById("studioUiRefreshToggleBtn")) return;
|
|
858
|
-
const button = makeStudioUiRefreshElement("button", "footer-compact-btn studio-ui-refresh-toggle", studioUiRefreshEnabled ? "UI:
|
|
858
|
+
const button = makeStudioUiRefreshElement("button", "footer-compact-btn studio-ui-refresh-toggle", studioUiRefreshEnabled ? "UI: Fresh" : "UI: Classic");
|
|
859
859
|
button.id = "studioUiRefreshToggleBtn";
|
|
860
860
|
button.type = "button";
|
|
861
861
|
button.title = studioUiRefreshEnabled
|
|
@@ -1265,7 +1265,24 @@
|
|
|
1265
1265
|
}
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
|
-
function
|
|
1268
|
+
function formatCompactNumber(value) {
|
|
1269
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "?";
|
|
1270
|
+
const sign = value < 0 ? "-" : "";
|
|
1271
|
+
const abs = Math.abs(value);
|
|
1272
|
+
if (abs < 1000) return sign + formatNumber(abs);
|
|
1273
|
+
const units = [
|
|
1274
|
+
{ divisor: 1_000_000_000, suffix: "B" },
|
|
1275
|
+
{ divisor: 1_000_000, suffix: "M" },
|
|
1276
|
+
{ divisor: 1_000, suffix: "k" },
|
|
1277
|
+
];
|
|
1278
|
+
const unit = units.find((entry) => abs >= entry.divisor) || units[units.length - 1];
|
|
1279
|
+
const scaled = abs / unit.divisor;
|
|
1280
|
+
const decimals = scaled >= 100 ? 0 : 1;
|
|
1281
|
+
return sign + scaled.toFixed(decimals).replace(/\.0$/, "") + unit.suffix;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function formatContextUsageText(compact) {
|
|
1285
|
+
const formatContextNumber = compact ? formatCompactNumber : formatNumber;
|
|
1269
1286
|
const hasWindow = typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0;
|
|
1270
1287
|
const hasTokens = typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens >= 0;
|
|
1271
1288
|
let percentValue = typeof contextPercent === "number" && Number.isFinite(contextPercent)
|
|
@@ -1280,12 +1297,12 @@
|
|
|
1280
1297
|
return "Context: unknown";
|
|
1281
1298
|
}
|
|
1282
1299
|
if (!hasTokens && hasWindow) {
|
|
1283
|
-
return "Context: ? / " +
|
|
1300
|
+
return "Context: ? / " + formatContextNumber(contextWindow);
|
|
1284
1301
|
}
|
|
1285
1302
|
|
|
1286
|
-
let text = "Context: " +
|
|
1303
|
+
let text = "Context: " + formatContextNumber(contextTokens);
|
|
1287
1304
|
if (hasWindow) {
|
|
1288
|
-
text += " / " +
|
|
1305
|
+
text += " / " + formatContextNumber(contextWindow);
|
|
1289
1306
|
}
|
|
1290
1307
|
if (percentValue != null && Number.isFinite(percentValue)) {
|
|
1291
1308
|
const bounded = Math.max(0, Math.min(100, percentValue));
|
|
@@ -1515,14 +1532,16 @@
|
|
|
1515
1532
|
function updateFooterMeta() {
|
|
1516
1533
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
1517
1534
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
1518
|
-
const contextText = formatContextUsageText();
|
|
1535
|
+
const contextText = formatContextUsageText(true);
|
|
1536
|
+
const contextTitleText = formatContextUsageText(false);
|
|
1519
1537
|
const text = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextText;
|
|
1538
|
+
const titleText = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextTitleText;
|
|
1520
1539
|
if (footerMetaTextEl) {
|
|
1521
1540
|
footerMetaTextEl.textContent = text;
|
|
1522
|
-
footerMetaTextEl.title =
|
|
1541
|
+
footerMetaTextEl.title = titleText;
|
|
1523
1542
|
} else if (footerMetaEl) {
|
|
1524
1543
|
footerMetaEl.textContent = text;
|
|
1525
|
-
footerMetaEl.title =
|
|
1544
|
+
footerMetaEl.title = titleText;
|
|
1526
1545
|
}
|
|
1527
1546
|
updateDocumentTitle();
|
|
1528
1547
|
}
|
|
@@ -5750,6 +5769,87 @@
|
|
|
5750
5769
|
};
|
|
5751
5770
|
}
|
|
5752
5771
|
|
|
5772
|
+
function getDiffFileLabelForLine(source, lineNumber) {
|
|
5773
|
+
const lines = String(source || "").replace(/\r\n/g, "\n").split("\n");
|
|
5774
|
+
const safeLine = Math.max(1, Math.min(Math.floor(Number(lineNumber) || 1), Math.max(1, lines.length)));
|
|
5775
|
+
let currentFile = "";
|
|
5776
|
+
for (let i = 0; i < safeLine; i += 1) {
|
|
5777
|
+
const line = String(lines[i] || "");
|
|
5778
|
+
const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+?)\s*$/);
|
|
5779
|
+
if (diffMatch) {
|
|
5780
|
+
currentFile = diffMatch[2] || diffMatch[1] || currentFile;
|
|
5781
|
+
continue;
|
|
5782
|
+
}
|
|
5783
|
+
const plusMatch = line.match(/^\+\+\+\s+(?:b\/)?(.+)\s*$/);
|
|
5784
|
+
if (plusMatch && plusMatch[1] && plusMatch[1] !== "/dev/null") {
|
|
5785
|
+
currentFile = plusMatch[1];
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
return currentFile.trim();
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
function getReviewNotePromptFileLabel(note, source) {
|
|
5792
|
+
if (sourceState && sourceState.path) return String(sourceState.path);
|
|
5793
|
+
const bounds = getResolvedReviewNoteLineBounds(note, source);
|
|
5794
|
+
const diffFile = bounds ? getDiffFileLabelForLine(source, bounds.lineStart) : "";
|
|
5795
|
+
if (diffFile) return diffFile;
|
|
5796
|
+
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
5797
|
+
return descriptor && descriptor.fileBacked ? descriptor.label : "";
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
function formatReviewNotePromptLineRange(bounds, note) {
|
|
5801
|
+
const start = bounds ? bounds.lineStart : Math.max(1, Number(note && note.lineStart) || 1);
|
|
5802
|
+
const end = bounds ? bounds.lineEnd : Math.max(start, Number(note && note.lineEnd) || start);
|
|
5803
|
+
return start === end ? "L" + start : ("L" + start + "-L" + end);
|
|
5804
|
+
}
|
|
5805
|
+
|
|
5806
|
+
function buildReviewNotesPrompt() {
|
|
5807
|
+
const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
5808
|
+
const notes = getDisplayReviewNotes().filter((note) => String(note && note.text ? note.text : "").trim());
|
|
5809
|
+
if (!notes.length) return "";
|
|
5810
|
+
|
|
5811
|
+
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
5812
|
+
const documentLabel = descriptor && descriptor.label ? descriptor.label : (sourceState && sourceState.label ? sourceState.label : "Studio document");
|
|
5813
|
+
const parts = [
|
|
5814
|
+
"Please address the following Studio comments. Use the file names and line numbers as anchors. The full document is not included here, only the comments and their anchors.",
|
|
5815
|
+
"Document: " + documentLabel,
|
|
5816
|
+
"",
|
|
5817
|
+
"## Comments",
|
|
5818
|
+
];
|
|
5819
|
+
|
|
5820
|
+
notes.forEach((note, index) => {
|
|
5821
|
+
const bounds = getResolvedReviewNoteLineBounds(note, source);
|
|
5822
|
+
const fileLabel = getReviewNotePromptFileLabel(note, source);
|
|
5823
|
+
const location = (fileLabel ? (fileLabel + ":") : "") + formatReviewNotePromptLineRange(bounds, note);
|
|
5824
|
+
const comment = String(note && note.text ? note.text : "").trim();
|
|
5825
|
+
const anchor = String(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "")
|
|
5826
|
+
.replace(/\s+/g, " ")
|
|
5827
|
+
.trim();
|
|
5828
|
+
parts.push(
|
|
5829
|
+
"### Comment " + (index + 1) + " — " + location,
|
|
5830
|
+
"",
|
|
5831
|
+
comment,
|
|
5832
|
+
);
|
|
5833
|
+
if (anchor) {
|
|
5834
|
+
parts.push("", "> " + anchor.replace(/\n/g, "\n> "));
|
|
5835
|
+
}
|
|
5836
|
+
parts.push("");
|
|
5837
|
+
});
|
|
5838
|
+
|
|
5839
|
+
return parts.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
5840
|
+
}
|
|
5841
|
+
|
|
5842
|
+
function loadReviewNotesPromptIntoEditor() {
|
|
5843
|
+
const prompt = buildReviewNotesPrompt();
|
|
5844
|
+
if (!prompt.trim()) {
|
|
5845
|
+
setStatus("No non-empty comments to load as a prompt.", "warning");
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5848
|
+
setEditorText(prompt, { preserveScroll: false, preserveSelection: false });
|
|
5849
|
+
setSourceState({ source: "blank", label: "comments prompt", path: null });
|
|
5850
|
+
setStatus("Loaded comments prompt into editor.", "success");
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5753
5853
|
function buildReviewNoteLineMap(text) {
|
|
5754
5854
|
const source = String(text || "");
|
|
5755
5855
|
const lineMap = new Map();
|
|
@@ -8573,12 +8673,19 @@
|
|
|
8573
8673
|
? "Select preview text and use Comment for a local preview-anchored comment."
|
|
8574
8674
|
: "Switch to Editor (Raw) to comment on the current line.");
|
|
8575
8675
|
}
|
|
8676
|
+
if (reviewNotesPromptBtn) {
|
|
8677
|
+
const promptCandidates = reviewNotes.filter((note) => String(note && note.text ? note.text : "").trim());
|
|
8678
|
+
reviewNotesPromptBtn.disabled = uiBusy || promptCandidates.length === 0;
|
|
8679
|
+
reviewNotesPromptBtn.title = promptCandidates.length > 0
|
|
8680
|
+
? "Load local comments, line numbers, and file labels into the editor as a prompt."
|
|
8681
|
+
: "No non-empty local comments to load as a prompt.";
|
|
8682
|
+
}
|
|
8576
8683
|
if (reviewNotesInlineAllBtn) {
|
|
8577
8684
|
const currentText = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
|
|
8578
8685
|
const toggleCandidates = getDisplayReviewNotes().filter((note) => getReviewNoteInlineState(note, currentText).canToggle);
|
|
8579
8686
|
const allInline = toggleCandidates.length > 0 && toggleCandidates.every((note) => getReviewNoteInlineState(note, currentText).exists);
|
|
8580
8687
|
reviewNotesInlineAllBtn.disabled = uiBusy || toggleCandidates.length === 0;
|
|
8581
|
-
reviewNotesInlineAllBtn.textContent = allInline ? "
|
|
8688
|
+
reviewNotesInlineAllBtn.textContent = allInline ? "Inline: On" : "Inline: Off";
|
|
8582
8689
|
reviewNotesInlineAllBtn.setAttribute("aria-pressed", allInline ? "true" : "false");
|
|
8583
8690
|
reviewNotesInlineAllBtn.title = allInline
|
|
8584
8691
|
? "Inline annotations derived from all non-empty comments are currently on. Click to remove them."
|
|
@@ -9705,6 +9812,7 @@
|
|
|
9705
9812
|
setBusy(false);
|
|
9706
9813
|
setWsState("Ready");
|
|
9707
9814
|
|
|
9815
|
+
pendingResponseScrollReset = true;
|
|
9708
9816
|
let appliedFromHistory = false;
|
|
9709
9817
|
if (Array.isArray(message.responseHistory)) {
|
|
9710
9818
|
appliedFromHistory = setResponseHistory(message.responseHistory, {
|
|
@@ -9733,6 +9841,9 @@
|
|
|
9733
9841
|
if (pendingRequestId) return;
|
|
9734
9842
|
|
|
9735
9843
|
const hasHistory = Array.isArray(message.responseHistory);
|
|
9844
|
+
if (followLatest) {
|
|
9845
|
+
pendingResponseScrollReset = true;
|
|
9846
|
+
}
|
|
9736
9847
|
if (hasHistory) {
|
|
9737
9848
|
setResponseHistory(message.responseHistory, {
|
|
9738
9849
|
autoSelectLatest: followLatest,
|
|
@@ -9756,7 +9867,7 @@
|
|
|
9756
9867
|
return;
|
|
9757
9868
|
}
|
|
9758
9869
|
|
|
9759
|
-
if (!hasHistory && applyLatestPayload(payload)) {
|
|
9870
|
+
if (!hasHistory && applyLatestPayload(payload, { resetScroll: true })) {
|
|
9760
9871
|
queuedLatestResponse = null;
|
|
9761
9872
|
updateResultActionButtons();
|
|
9762
9873
|
setStatus("Updated from latest response.", "success");
|
|
@@ -10926,6 +11037,12 @@
|
|
|
10926
11037
|
});
|
|
10927
11038
|
}
|
|
10928
11039
|
|
|
11040
|
+
if (reviewNotesPromptBtn) {
|
|
11041
|
+
reviewNotesPromptBtn.addEventListener("click", () => {
|
|
11042
|
+
loadReviewNotesPromptIntoEditor();
|
|
11043
|
+
});
|
|
11044
|
+
}
|
|
11045
|
+
|
|
10929
11046
|
if (reviewNotesInlineAllBtn) {
|
|
10930
11047
|
reviewNotesInlineAllBtn.addEventListener("click", () => {
|
|
10931
11048
|
toggleAllReviewNotesInlineAnnotations();
|
package/client/studio.css
CHANGED
|
@@ -2113,7 +2113,13 @@
|
|
|
2113
2113
|
|
|
2114
2114
|
.review-notes-dock-footer .scratchpad-actions {
|
|
2115
2115
|
width: 100%;
|
|
2116
|
-
|
|
2116
|
+
gap: 6px;
|
|
2117
|
+
justify-content: flex-end;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
.review-notes-dock-footer .scratchpad-actions button {
|
|
2121
|
+
padding: 5px 7px;
|
|
2122
|
+
font-size: 11px;
|
|
2117
2123
|
}
|
|
2118
2124
|
|
|
2119
2125
|
.outline-list {
|
|
@@ -2315,48 +2321,48 @@
|
|
|
2315
2321
|
}
|
|
2316
2322
|
}
|
|
2317
2323
|
|
|
2318
|
-
/*
|
|
2324
|
+
/* Default refreshed Studio layout. Classic layout remains available with ?uiRefresh=0 or the footer UI switch. */
|
|
2319
2325
|
body.studio-ui-refresh {
|
|
2320
|
-
font-size:
|
|
2326
|
+
font-size: 13px;
|
|
2321
2327
|
}
|
|
2322
2328
|
|
|
2323
2329
|
body.studio-ui-refresh > header {
|
|
2324
|
-
padding:
|
|
2325
|
-
gap:
|
|
2330
|
+
padding: 8px 12px;
|
|
2331
|
+
gap: 9px;
|
|
2326
2332
|
}
|
|
2327
2333
|
|
|
2328
2334
|
body.studio-ui-refresh h1 {
|
|
2329
|
-
font-size:
|
|
2335
|
+
font-size: 16px;
|
|
2330
2336
|
}
|
|
2331
2337
|
|
|
2332
2338
|
body.studio-ui-refresh .app-logo {
|
|
2333
|
-
font-size:
|
|
2339
|
+
font-size: 18px;
|
|
2334
2340
|
}
|
|
2335
2341
|
|
|
2336
2342
|
body.studio-ui-refresh .app-subtitle {
|
|
2337
|
-
font-size:
|
|
2343
|
+
font-size: 10px;
|
|
2338
2344
|
}
|
|
2339
2345
|
|
|
2340
2346
|
body.studio-ui-refresh > header .controls {
|
|
2341
|
-
gap:
|
|
2347
|
+
gap: 5px;
|
|
2342
2348
|
}
|
|
2343
2349
|
|
|
2344
2350
|
body.studio-ui-refresh > header button,
|
|
2345
2351
|
body.studio-ui-refresh > header .file-label,
|
|
2346
2352
|
body.studio-ui-refresh #responseActions button,
|
|
2347
2353
|
body.studio-ui-refresh #responseActions select {
|
|
2348
|
-
padding:
|
|
2349
|
-
font-size:
|
|
2354
|
+
padding: 5px 7px;
|
|
2355
|
+
font-size: 12px;
|
|
2350
2356
|
}
|
|
2351
2357
|
|
|
2352
2358
|
body.studio-ui-refresh main {
|
|
2353
|
-
gap:
|
|
2354
|
-
padding:
|
|
2359
|
+
gap: 9px;
|
|
2360
|
+
padding: 9px;
|
|
2355
2361
|
}
|
|
2356
2362
|
|
|
2357
2363
|
body.studio-ui-refresh footer {
|
|
2358
|
-
padding:
|
|
2359
|
-
font-size:
|
|
2364
|
+
padding: 7px 9px;
|
|
2365
|
+
font-size: 11px;
|
|
2360
2366
|
}
|
|
2361
2367
|
|
|
2362
2368
|
.studio-ui-refresh-toggle {
|
|
@@ -2378,9 +2384,9 @@
|
|
|
2378
2384
|
position: relative;
|
|
2379
2385
|
z-index: 30;
|
|
2380
2386
|
display: grid;
|
|
2381
|
-
gap:
|
|
2387
|
+
gap: 6px;
|
|
2382
2388
|
background: transparent;
|
|
2383
|
-
padding:
|
|
2389
|
+
padding: 7px 9px 6px;
|
|
2384
2390
|
overflow: visible;
|
|
2385
2391
|
}
|
|
2386
2392
|
|
|
@@ -2402,7 +2408,7 @@
|
|
|
2402
2408
|
body.studio-ui-refresh .studio-refresh-action-line {
|
|
2403
2409
|
display: flex;
|
|
2404
2410
|
align-items: center;
|
|
2405
|
-
gap:
|
|
2411
|
+
gap: 6px;
|
|
2406
2412
|
min-width: 0;
|
|
2407
2413
|
flex-wrap: wrap;
|
|
2408
2414
|
}
|
|
@@ -2412,7 +2418,7 @@
|
|
|
2412
2418
|
display: grid;
|
|
2413
2419
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
2414
2420
|
align-items: center;
|
|
2415
|
-
gap:
|
|
2421
|
+
gap: 9px;
|
|
2416
2422
|
min-width: 0;
|
|
2417
2423
|
}
|
|
2418
2424
|
|
|
@@ -2420,8 +2426,8 @@
|
|
|
2420
2426
|
display: grid;
|
|
2421
2427
|
grid-template-columns: auto minmax(0, 1fr);
|
|
2422
2428
|
align-items: center;
|
|
2423
|
-
column-gap:
|
|
2424
|
-
row-gap:
|
|
2429
|
+
column-gap: 9px;
|
|
2430
|
+
row-gap: 5px;
|
|
2425
2431
|
}
|
|
2426
2432
|
|
|
2427
2433
|
body.studio-ui-refresh .studio-refresh-title-group,
|
|
@@ -2437,7 +2443,7 @@
|
|
|
2437
2443
|
}
|
|
2438
2444
|
|
|
2439
2445
|
body.studio-ui-refresh .studio-refresh-title-group {
|
|
2440
|
-
gap:
|
|
2446
|
+
gap: 2px;
|
|
2441
2447
|
}
|
|
2442
2448
|
|
|
2443
2449
|
body.studio-ui-refresh .studio-refresh-pane-tools {
|
|
@@ -2448,15 +2454,15 @@
|
|
|
2448
2454
|
body.studio-ui-refresh .studio-refresh-sep {
|
|
2449
2455
|
display: inline-block;
|
|
2450
2456
|
width: 1px;
|
|
2451
|
-
height:
|
|
2457
|
+
height: 16px;
|
|
2452
2458
|
background: var(--border-muted);
|
|
2453
2459
|
margin: 0 1px;
|
|
2454
2460
|
flex: 0 0 1px;
|
|
2455
2461
|
}
|
|
2456
2462
|
|
|
2457
2463
|
body.studio-ui-refresh .studio-refresh-icon {
|
|
2458
|
-
width:
|
|
2459
|
-
height:
|
|
2464
|
+
width: 15px;
|
|
2465
|
+
height: 15px;
|
|
2460
2466
|
stroke: currentColor;
|
|
2461
2467
|
stroke-width: 1.85;
|
|
2462
2468
|
stroke-linecap: round;
|
|
@@ -2473,7 +2479,7 @@
|
|
|
2473
2479
|
body.studio-ui-refresh .studio-refresh-toolbar select {
|
|
2474
2480
|
border-color: transparent;
|
|
2475
2481
|
background: transparent;
|
|
2476
|
-
font-size:
|
|
2482
|
+
font-size: 13px;
|
|
2477
2483
|
}
|
|
2478
2484
|
|
|
2479
2485
|
body.studio-ui-refresh #leftSectionHeader select:hover,
|
|
@@ -2487,7 +2493,7 @@
|
|
|
2487
2493
|
|
|
2488
2494
|
body.studio-ui-refresh #leftSectionHeader #editorViewSelect,
|
|
2489
2495
|
body.studio-ui-refresh #rightSectionHeader #rightViewSelect {
|
|
2490
|
-
font-size:
|
|
2496
|
+
font-size: 14px;
|
|
2491
2497
|
font-weight: 750;
|
|
2492
2498
|
padding: 3px 5px;
|
|
2493
2499
|
max-width: 230px;
|
|
@@ -2495,7 +2501,7 @@
|
|
|
2495
2501
|
|
|
2496
2502
|
body.studio-ui-refresh .studio-refresh-static-title {
|
|
2497
2503
|
color: var(--text);
|
|
2498
|
-
font-size:
|
|
2504
|
+
font-size: 14px;
|
|
2499
2505
|
font-weight: 700;
|
|
2500
2506
|
padding: 3px 5px;
|
|
2501
2507
|
max-width: 230px;
|
|
@@ -2504,9 +2510,9 @@
|
|
|
2504
2510
|
|
|
2505
2511
|
body.studio-ui-refresh #leftFocusBtn,
|
|
2506
2512
|
body.studio-ui-refresh #rightFocusBtn {
|
|
2507
|
-
width:
|
|
2508
|
-
min-width:
|
|
2509
|
-
min-height:
|
|
2513
|
+
width: 29px;
|
|
2514
|
+
min-width: 29px;
|
|
2515
|
+
min-height: 29px;
|
|
2510
2516
|
padding: 0;
|
|
2511
2517
|
color: var(--muted);
|
|
2512
2518
|
align-items: center;
|
|
@@ -2518,8 +2524,8 @@
|
|
|
2518
2524
|
body.studio-ui-refresh #resourceDirLabel {
|
|
2519
2525
|
border-color: transparent;
|
|
2520
2526
|
background: transparent;
|
|
2521
|
-
padding:
|
|
2522
|
-
font-size:
|
|
2527
|
+
padding: 3px 5px;
|
|
2528
|
+
font-size: 13px;
|
|
2523
2529
|
border-radius: 8px;
|
|
2524
2530
|
}
|
|
2525
2531
|
|
|
@@ -2548,11 +2554,11 @@
|
|
|
2548
2554
|
body.studio-ui-refresh #syncBadge {
|
|
2549
2555
|
display: inline-flex;
|
|
2550
2556
|
align-items: center;
|
|
2551
|
-
gap:
|
|
2552
|
-
min-height:
|
|
2557
|
+
gap: 5px;
|
|
2558
|
+
min-height: 24px;
|
|
2553
2559
|
border: 0;
|
|
2554
2560
|
border-radius: 999px;
|
|
2555
|
-
padding:
|
|
2561
|
+
padding: 2px 8px;
|
|
2556
2562
|
background: var(--panel-2);
|
|
2557
2563
|
color: var(--muted);
|
|
2558
2564
|
opacity: 1;
|
|
@@ -2564,8 +2570,8 @@
|
|
|
2564
2570
|
|
|
2565
2571
|
body.studio-ui-refresh #syncBadge::before {
|
|
2566
2572
|
content: "";
|
|
2567
|
-
width:
|
|
2568
|
-
height:
|
|
2573
|
+
width: 6px;
|
|
2574
|
+
height: 6px;
|
|
2569
2575
|
border-radius: 999px;
|
|
2570
2576
|
background: var(--success, #22c55e);
|
|
2571
2577
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--success, #22c55e) 14%, transparent);
|
|
@@ -2577,8 +2583,8 @@
|
|
|
2577
2583
|
body.studio-ui-refresh #exportPdfBtn,
|
|
2578
2584
|
body.studio-ui-refresh .studio-refresh-tool-tab {
|
|
2579
2585
|
font-weight: 500;
|
|
2580
|
-
padding:
|
|
2581
|
-
border-radius:
|
|
2586
|
+
padding: 5px 8px;
|
|
2587
|
+
border-radius: 8px;
|
|
2582
2588
|
}
|
|
2583
2589
|
|
|
2584
2590
|
body.studio-ui-refresh #exportPdfBtn {
|
|
@@ -2611,33 +2617,99 @@
|
|
|
2611
2617
|
}
|
|
2612
2618
|
|
|
2613
2619
|
body.studio-ui-refresh .source-body {
|
|
2614
|
-
padding:
|
|
2620
|
+
padding: 7px;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
body.studio-ui-refresh textarea {
|
|
2624
|
+
font-size: 12px;
|
|
2625
|
+
line-height: 1.42;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
body.studio-ui-refresh .editor-highlight,
|
|
2629
|
+
body.studio-ui-refresh .editor-line-number-measure {
|
|
2630
|
+
font-size: 12px;
|
|
2631
|
+
line-height: 1.42;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
body.studio-ui-refresh #sourceText,
|
|
2635
|
+
body.studio-ui-refresh .editor-highlight {
|
|
2636
|
+
padding: 9px 9px 9px calc(9px + var(--editor-review-note-gutter-width) + var(--editor-line-number-gutter-width));
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
body.studio-ui-refresh .editor-line-number-gutter-content {
|
|
2640
|
+
padding: 9px 7px 9px 0;
|
|
2641
|
+
font-size: 11px;
|
|
2642
|
+
line-height: 1.42;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
body.studio-ui-refresh .editor-review-note-gutter-content {
|
|
2646
|
+
padding: 9px 4px;
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
body.studio-ui-refresh .panel-scroll {
|
|
2650
|
+
padding: 11px;
|
|
2651
|
+
line-height: 1.48;
|
|
2652
|
+
font-size: 13px;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
body.studio-ui-refresh .rendered-markdown {
|
|
2656
|
+
line-height: 1.52;
|
|
2657
|
+
font-size: 13.5px;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
body.studio-ui-refresh .plain-markdown,
|
|
2661
|
+
body.studio-ui-refresh .response-markdown-highlight {
|
|
2662
|
+
font-size: 12px;
|
|
2663
|
+
line-height: 1.42;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
body.studio-ui-refresh .rendered-markdown pre {
|
|
2667
|
+
padding: 11px 13px;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
body.studio-ui-refresh .trace-panel,
|
|
2671
|
+
body.studio-ui-refresh .trace-card,
|
|
2672
|
+
body.studio-ui-refresh .trace-toolbar,
|
|
2673
|
+
body.studio-ui-refresh .trace-summary,
|
|
2674
|
+
body.studio-ui-refresh .trace-controls,
|
|
2675
|
+
body.studio-ui-refresh .trace-card-header {
|
|
2676
|
+
gap: 7px;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
body.studio-ui-refresh .trace-card {
|
|
2680
|
+
padding: 9px 11px;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
body.studio-ui-refresh .trace-output {
|
|
2684
|
+
padding: 9px 10px;
|
|
2685
|
+
font-size: 12px;
|
|
2686
|
+
line-height: 1.42;
|
|
2615
2687
|
}
|
|
2616
2688
|
|
|
2617
2689
|
body.studio-ui-refresh .studio-refresh-toolbar {
|
|
2618
2690
|
position: relative;
|
|
2619
|
-
padding:
|
|
2691
|
+
padding: 8px 10px 9px;
|
|
2620
2692
|
overflow: visible;
|
|
2621
2693
|
}
|
|
2622
2694
|
|
|
2623
2695
|
body.studio-ui-refresh .studio-refresh-toolbar-main {
|
|
2624
2696
|
display: grid;
|
|
2625
2697
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
2626
|
-
gap:
|
|
2698
|
+
gap: 10px;
|
|
2627
2699
|
align-items: start;
|
|
2628
2700
|
min-width: 0;
|
|
2629
2701
|
}
|
|
2630
2702
|
|
|
2631
2703
|
body.studio-ui-refresh .studio-refresh-toolbar-actions {
|
|
2632
2704
|
display: grid;
|
|
2633
|
-
gap:
|
|
2705
|
+
gap: 6px;
|
|
2634
2706
|
justify-items: start;
|
|
2635
2707
|
min-width: 0;
|
|
2636
2708
|
}
|
|
2637
2709
|
|
|
2638
2710
|
body.studio-ui-refresh .studio-refresh-toolbar-state {
|
|
2639
2711
|
display: grid;
|
|
2640
|
-
gap:
|
|
2712
|
+
gap: 6px;
|
|
2641
2713
|
justify-items: end;
|
|
2642
2714
|
align-content: start;
|
|
2643
2715
|
min-width: max-content;
|
|
@@ -2646,18 +2718,18 @@
|
|
|
2646
2718
|
body.studio-ui-refresh .studio-refresh-chip {
|
|
2647
2719
|
display: inline-flex;
|
|
2648
2720
|
align-items: center;
|
|
2649
|
-
gap:
|
|
2650
|
-
border-radius:
|
|
2721
|
+
gap: 5px;
|
|
2722
|
+
border-radius: 8px;
|
|
2651
2723
|
color: var(--text);
|
|
2652
2724
|
white-space: nowrap;
|
|
2653
|
-
padding:
|
|
2654
|
-
font-size:
|
|
2725
|
+
padding: 5px 8px;
|
|
2726
|
+
font-size: 13px;
|
|
2655
2727
|
}
|
|
2656
2728
|
|
|
2657
2729
|
body.studio-ui-refresh .studio-refresh-chip::after {
|
|
2658
2730
|
content: "⌄";
|
|
2659
2731
|
color: var(--muted);
|
|
2660
|
-
font-size:
|
|
2732
|
+
font-size: 14px;
|
|
2661
2733
|
line-height: 1;
|
|
2662
2734
|
transform: translateY(-1px);
|
|
2663
2735
|
}
|
|
@@ -2749,27 +2821,24 @@
|
|
|
2749
2821
|
}
|
|
2750
2822
|
|
|
2751
2823
|
body.studio-ui-refresh #copyDraftBtn:only-child {
|
|
2752
|
-
min-height:
|
|
2753
|
-
padding:
|
|
2824
|
+
min-height: 29px;
|
|
2825
|
+
padding: 4px 8px;
|
|
2754
2826
|
}
|
|
2755
2827
|
|
|
2756
2828
|
body.studio-ui-refresh #sendRunBtn,
|
|
2757
2829
|
body.studio-ui-refresh #queueSteerBtn,
|
|
2758
2830
|
body.studio-ui-refresh #loadResponseBtn:not([hidden]) {
|
|
2759
|
-
height:
|
|
2760
|
-
min-height:
|
|
2761
|
-
padding: 4px
|
|
2762
|
-
font-size:
|
|
2831
|
+
height: 28px;
|
|
2832
|
+
min-height: 28px;
|
|
2833
|
+
padding: 4px 9px;
|
|
2834
|
+
font-size: 12px;
|
|
2763
2835
|
line-height: 1.2;
|
|
2764
2836
|
border-radius: 8px;
|
|
2765
2837
|
}
|
|
2766
2838
|
|
|
2767
|
-
body.studio-ui-refresh #sendRunBtn
|
|
2768
|
-
min-width: 9.2rem;
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2839
|
+
body.studio-ui-refresh #sendRunBtn,
|
|
2771
2840
|
body.studio-ui-refresh #queueSteerBtn {
|
|
2772
|
-
min-width:
|
|
2841
|
+
min-width: 8.6rem;
|
|
2773
2842
|
}
|
|
2774
2843
|
|
|
2775
2844
|
body.studio-ui-refresh #queueSteerBtn:not(:disabled) {
|
package/index.ts
CHANGED
|
@@ -5764,34 +5764,74 @@ function createEmptyStudioTraceState(): StudioTraceState {
|
|
|
5764
5764
|
};
|
|
5765
5765
|
}
|
|
5766
5766
|
|
|
5767
|
+
function sanitizeStudioTraceOutputText(text: string): string {
|
|
5768
|
+
return String(text || "")
|
|
5769
|
+
.replace(/data:image\/([a-zA-Z0-9.+-]+);base64,[A-Za-z0-9+/=\r\n]+/g, (_match, subtype: string) => `[Image: image/${subtype || "unknown"} data omitted]`)
|
|
5770
|
+
.replace(/(\"(?:data|image|base64|content)\"\s*:\s*\")[A-Za-z0-9+/=]{1000,}(\")/g, "$1[base64 data omitted]$2")
|
|
5771
|
+
.replace(/\b[A-Za-z0-9+/]{3000,}={0,2}\b/g, "[base64 data omitted]");
|
|
5772
|
+
}
|
|
5773
|
+
|
|
5774
|
+
function isStudioTraceImageBlock(block: unknown): boolean {
|
|
5775
|
+
if (!block || typeof block !== "object") return false;
|
|
5776
|
+
const payload = block as Record<string, unknown>;
|
|
5777
|
+
const type = typeof payload.type === "string" ? payload.type.toLowerCase() : "";
|
|
5778
|
+
if (type.includes("image")) return true;
|
|
5779
|
+
const mime = typeof payload.mimeType === "string"
|
|
5780
|
+
? payload.mimeType
|
|
5781
|
+
: (typeof payload.media_type === "string" ? payload.media_type : "");
|
|
5782
|
+
if (mime.toLowerCase().startsWith("image/")) return true;
|
|
5783
|
+
const source = payload.source && typeof payload.source === "object" ? payload.source as Record<string, unknown> : null;
|
|
5784
|
+
const sourceMime = source && typeof source.media_type === "string" ? source.media_type : "";
|
|
5785
|
+
return sourceMime.toLowerCase().startsWith("image/");
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
function describeStudioTraceImageBlock(block: unknown): string {
|
|
5789
|
+
const payload = (block && typeof block === "object") ? block as Record<string, unknown> : {};
|
|
5790
|
+
const source = payload.source && typeof payload.source === "object" ? payload.source as Record<string, unknown> : null;
|
|
5791
|
+
const mime = typeof payload.mimeType === "string"
|
|
5792
|
+
? payload.mimeType
|
|
5793
|
+
: (typeof payload.media_type === "string"
|
|
5794
|
+
? payload.media_type
|
|
5795
|
+
: (source && typeof source.media_type === "string" ? source.media_type : "image"));
|
|
5796
|
+
return `[Image: ${mime || "image"} output omitted from Working view]`;
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
function stringifyStudioTraceObject(value: unknown): string {
|
|
5800
|
+
try {
|
|
5801
|
+
return sanitizeStudioTraceOutputText(JSON.stringify(value, (_key, item) => {
|
|
5802
|
+
if (typeof item === "string") {
|
|
5803
|
+
if (/^data:image\//i.test(item)) return "[image data URI omitted]";
|
|
5804
|
+
if (/^[A-Za-z0-9+/=]{1000,}$/.test(item)) return "[base64 data omitted]";
|
|
5805
|
+
}
|
|
5806
|
+
return item;
|
|
5807
|
+
}, 2));
|
|
5808
|
+
} catch {
|
|
5809
|
+
return sanitizeStudioTraceOutputText(String(value));
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
|
|
5767
5813
|
function formatStudioTraceOutput(result: unknown): string {
|
|
5768
5814
|
if (result == null) return "";
|
|
5769
|
-
if (typeof result === "string") return result;
|
|
5815
|
+
if (typeof result === "string") return sanitizeStudioTraceOutputText(result);
|
|
5770
5816
|
if (Array.isArray(result)) {
|
|
5771
5817
|
return result.map((item) => formatStudioTraceOutput(item)).filter(Boolean).join("\n");
|
|
5772
5818
|
}
|
|
5773
5819
|
if (typeof result === "object") {
|
|
5820
|
+
if (isStudioTraceImageBlock(result)) return describeStudioTraceImageBlock(result);
|
|
5774
5821
|
const payload = result as { content?: Array<{ type?: string; text?: string }> };
|
|
5775
5822
|
if (Array.isArray(payload.content)) {
|
|
5776
5823
|
return payload.content
|
|
5777
5824
|
.map((block) => {
|
|
5778
|
-
if (block
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
} catch {
|
|
5782
|
-
return String(block);
|
|
5783
|
-
}
|
|
5825
|
+
if (isStudioTraceImageBlock(block)) return describeStudioTraceImageBlock(block);
|
|
5826
|
+
if (block && block.type === "text" && typeof block.text === "string") return sanitizeStudioTraceOutputText(block.text);
|
|
5827
|
+
return stringifyStudioTraceObject(block);
|
|
5784
5828
|
})
|
|
5785
5829
|
.filter(Boolean)
|
|
5786
5830
|
.join("\n");
|
|
5787
5831
|
}
|
|
5788
|
-
|
|
5789
|
-
return JSON.stringify(result, null, 2);
|
|
5790
|
-
} catch {
|
|
5791
|
-
return String(result);
|
|
5792
|
-
}
|
|
5832
|
+
return stringifyStudioTraceObject(result);
|
|
5793
5833
|
}
|
|
5794
|
-
return String(result);
|
|
5834
|
+
return sanitizeStudioTraceOutputText(String(result));
|
|
5795
5835
|
}
|
|
5796
5836
|
|
|
5797
5837
|
function summarizeStudioTraceToolArgs(toolName: string, args: unknown): string | null {
|
|
@@ -6260,8 +6300,9 @@ ${cssVarsBlock}
|
|
|
6260
6300
|
<div id="reviewNotesList" class="review-notes-list" aria-live="polite"></div>
|
|
6261
6301
|
<div class="review-notes-dock-footer">
|
|
6262
6302
|
<div class="scratchpad-actions">
|
|
6263
|
-
<button id="reviewNotesAddBtn" type="button" title="Create a new local comment on the current editor line.">Line
|
|
6264
|
-
<button id="
|
|
6303
|
+
<button id="reviewNotesAddBtn" type="button" title="Create a new local comment on the current editor line.">Line</button>
|
|
6304
|
+
<button id="reviewNotesPromptBtn" type="button" title="Load local comments, line numbers, and file labels into the editor as a prompt.">Comments → prompt</button>
|
|
6305
|
+
<button id="reviewNotesInlineAllBtn" type="button" title="Toggle inline annotations for all non-empty comments.">Inline: Off</button>
|
|
6265
6306
|
<button id="reviewNotesDeleteAllBtn" type="button" title="Delete all local comments for this document or draft.">Delete all</button>
|
|
6266
6307
|
<button id="reviewNotesDoneBtn" type="button" title="Hide the comments rail.">Hide</button>
|
|
6267
6308
|
</div>
|
|
@@ -7228,6 +7269,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
7228
7269
|
promptTriggerText: descriptor.promptTriggerText,
|
|
7229
7270
|
};
|
|
7230
7271
|
queuedStudioDirectRequests.push(queuedRequest);
|
|
7272
|
+
|
|
7273
|
+
// Steering is delivered into the currently running Studio turn rather than
|
|
7274
|
+
// becoming a fully separate visible response request. Keep the active direct
|
|
7275
|
+
// request metadata aligned with the effective prompt chain so persisted
|
|
7276
|
+
// prompt metadata, response history, and "Load effective prompt" all refer
|
|
7277
|
+
// to the original run plus queued steering, not just the original run.
|
|
7278
|
+
if (activeRequest && activeRequest.kind === "direct") {
|
|
7279
|
+
activeRequest.prompt = descriptor.prompt;
|
|
7280
|
+
activeRequest.promptMode = descriptor.promptMode;
|
|
7281
|
+
activeRequest.promptTriggerKind = descriptor.promptTriggerKind;
|
|
7282
|
+
activeRequest.promptSteeringCount = descriptor.promptSteeringCount;
|
|
7283
|
+
activeRequest.promptTriggerText = descriptor.promptTriggerText;
|
|
7284
|
+
}
|
|
7285
|
+
|
|
7231
7286
|
return queuedRequest;
|
|
7232
7287
|
};
|
|
7233
7288
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|