pi-studio 0.5.2 → 0.5.4
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 +2 -1
- package/index.ts +307 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -88,6 +88,33 @@ All notable changes to `pi-studio` are documented here.
|
|
|
88
88
|
|
|
89
89
|
## [Unreleased]
|
|
90
90
|
|
|
91
|
+
## [0.5.4] — 2026-03-09
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- New right-pane **Thinking (Raw)** view for assistant/model thinking when available.
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
- Response history and latest-response syncing now preserve associated thinking content.
|
|
98
|
+
- In Thinking view, right-pane actions adapt to the selected reasoning trace:
|
|
99
|
+
- **Load thinking into editor**
|
|
100
|
+
- **Copy thinking text**
|
|
101
|
+
- thinking-aware reference/sync badges
|
|
102
|
+
|
|
103
|
+
## [0.5.3] — 2026-03-06
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- New terminal command: `/studio-current <path>` loads a file into currently open Studio tab(s) without opening a new browser session.
|
|
107
|
+
- `/studio --help` now includes `/studio-current` usage.
|
|
108
|
+
|
|
109
|
+
### Changed
|
|
110
|
+
- Footer compact action label is now **Compact**.
|
|
111
|
+
- Footer metadata now includes in-Studio npm update hint text when an update is available (`Update: installed → latest`).
|
|
112
|
+
- Update notification timing now runs after Studio open notifications, so the update message is not immediately overwritten.
|
|
113
|
+
- Slash-command autocomplete order now lists `/studio` before `/studio-current`.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
- Removed low-value terminal toasts for Studio websocket connect/disconnect that could overwrite more important notifications.
|
|
117
|
+
|
|
91
118
|
## [0.5.2] — 2026-03-06
|
|
92
119
|
|
|
93
120
|
### Changed
|
package/README.md
CHANGED
|
@@ -14,11 +14,12 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
14
14
|
|
|
15
15
|
## What it does
|
|
16
16
|
|
|
17
|
-
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Editor Preview** (right)
|
|
17
|
+
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Thinking/Editor Preview** (right)
|
|
18
18
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
19
19
|
- Browses response history (`Prev/Next`) and loads either:
|
|
20
20
|
- response text
|
|
21
21
|
- critique notes/full critique
|
|
22
|
+
- assistant thinking (when available)
|
|
22
23
|
- the prompt that generated a selected response
|
|
23
24
|
- Supports an annotation workflow for `[an: ...]` markers:
|
|
24
25
|
- inserts/removes the annotated-reply header
|
package/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ interface ActiveStudioRequest {
|
|
|
32
32
|
|
|
33
33
|
interface LastStudioResponse {
|
|
34
34
|
markdown: string;
|
|
35
|
+
thinking: string | null;
|
|
35
36
|
timestamp: number;
|
|
36
37
|
kind: StudioRequestKind;
|
|
37
38
|
}
|
|
@@ -39,6 +40,7 @@ interface LastStudioResponse {
|
|
|
39
40
|
interface StudioResponseHistoryItem {
|
|
40
41
|
id: string;
|
|
41
42
|
markdown: string;
|
|
43
|
+
thinking: string | null;
|
|
42
44
|
timestamp: number;
|
|
43
45
|
kind: StudioRequestKind;
|
|
44
46
|
prompt: string | null;
|
|
@@ -1259,6 +1261,27 @@ function extractAssistantText(message: unknown): string | null {
|
|
|
1259
1261
|
return text.length > 0 ? text : null;
|
|
1260
1262
|
}
|
|
1261
1263
|
|
|
1264
|
+
function extractAssistantThinking(message: unknown): string | null {
|
|
1265
|
+
const msg = message as {
|
|
1266
|
+
role?: string;
|
|
1267
|
+
content?: Array<{ type?: string; thinking?: string }> | string;
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
if (!msg || msg.role !== "assistant" || !Array.isArray(msg.content)) return null;
|
|
1271
|
+
|
|
1272
|
+
const blocks: string[] = [];
|
|
1273
|
+
for (const part of msg.content) {
|
|
1274
|
+
if (!part || typeof part !== "object") continue;
|
|
1275
|
+
if (part.type !== "thinking") continue;
|
|
1276
|
+
if (typeof part.thinking === "string" && part.thinking.trim()) {
|
|
1277
|
+
blocks.push(part.thinking);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const thinking = blocks.join("\n\n").trim();
|
|
1282
|
+
return thinking.length > 0 ? thinking : null;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1262
1285
|
function extractLatestAssistantFromEntries(entries: SessionEntry[]): string | null {
|
|
1263
1286
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1264
1287
|
const entry = entries[i];
|
|
@@ -1330,9 +1353,11 @@ function buildResponseHistoryFromEntries(entries: SessionEntry[], limit = RESPON
|
|
|
1330
1353
|
if (role !== "assistant") continue;
|
|
1331
1354
|
const markdown = extractAssistantText(message);
|
|
1332
1355
|
if (!markdown) continue;
|
|
1356
|
+
const thinking = extractAssistantThinking(message);
|
|
1333
1357
|
history.push({
|
|
1334
1358
|
id: typeof (entry as { id?: unknown }).id === "string" ? (entry as { id: string }).id : randomUUID(),
|
|
1335
1359
|
markdown,
|
|
1360
|
+
thinking,
|
|
1336
1361
|
timestamp: parseEntryTimestamp((entry as { timestamp?: unknown }).timestamp),
|
|
1337
1362
|
kind: inferStudioResponseKind(markdown),
|
|
1338
1363
|
prompt: lastUserPrompt,
|
|
@@ -2889,6 +2914,7 @@ ${cssVarsBlock}
|
|
|
2889
2914
|
<option value="markdown">Response (Raw)</option>
|
|
2890
2915
|
<option value="preview" selected>Response (Preview)</option>
|
|
2891
2916
|
<option value="editor-preview">Editor (Preview)</option>
|
|
2917
|
+
<option value="thinking">Thinking (Raw)</option>
|
|
2892
2918
|
</select>
|
|
2893
2919
|
</div>
|
|
2894
2920
|
<div class="section-header-actions">
|
|
@@ -2931,7 +2957,7 @@ ${cssVarsBlock}
|
|
|
2931
2957
|
|
|
2932
2958
|
<footer>
|
|
2933
2959
|
<span id="statusLine"><span id="statusSpinner" aria-hidden="true"> </span><span id="status">Booting studio…</span></span>
|
|
2934
|
-
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text">Model: ${initialModel} · Terminal: ${initialTerminal} · Context: unknown</span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact
|
|
2960
|
+
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text">Model: ${initialModel} · Terminal: ${initialTerminal} · Context: unknown</span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact</button></span>
|
|
2935
2961
|
<span class="shortcut-hint">Focus pane: Cmd/Ctrl+Esc (or F10), Esc to exit · Run editor text: Cmd/Ctrl+Enter</span>
|
|
2936
2962
|
</footer>
|
|
2937
2963
|
|
|
@@ -3043,11 +3069,13 @@ ${cssVarsBlock}
|
|
|
3043
3069
|
let followLatest = true;
|
|
3044
3070
|
let queuedLatestResponse = null;
|
|
3045
3071
|
let latestResponseMarkdown = "";
|
|
3072
|
+
let latestResponseThinking = "";
|
|
3046
3073
|
let latestResponseTimestamp = 0;
|
|
3047
3074
|
let latestResponseKind = "annotation";
|
|
3048
3075
|
let latestResponseIsStructuredCritique = false;
|
|
3049
3076
|
let latestResponseHasContent = false;
|
|
3050
3077
|
let latestResponseNormalized = "";
|
|
3078
|
+
let latestResponseThinkingNormalized = "";
|
|
3051
3079
|
let latestCritiqueNotes = "";
|
|
3052
3080
|
let latestCritiqueNotesNormalized = "";
|
|
3053
3081
|
let responseHistory = [];
|
|
@@ -3065,6 +3093,8 @@ ${cssVarsBlock}
|
|
|
3065
3093
|
let contextTokens = null;
|
|
3066
3094
|
let contextWindow = null;
|
|
3067
3095
|
let contextPercent = null;
|
|
3096
|
+
let updateInstalledVersion = null;
|
|
3097
|
+
let updateLatestVersion = null;
|
|
3068
3098
|
|
|
3069
3099
|
function parseFiniteNumber(value) {
|
|
3070
3100
|
if (value == null || value === "") return null;
|
|
@@ -3072,6 +3102,12 @@ ${cssVarsBlock}
|
|
|
3072
3102
|
return Number.isFinite(parsed) ? parsed : null;
|
|
3073
3103
|
}
|
|
3074
3104
|
|
|
3105
|
+
function parseNonEmptyString(value) {
|
|
3106
|
+
if (typeof value !== "string") return null;
|
|
3107
|
+
const trimmed = value.trim();
|
|
3108
|
+
return trimmed ? trimmed : null;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3075
3111
|
contextTokens = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextTokens : null);
|
|
3076
3112
|
contextWindow = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextWindow : null);
|
|
3077
3113
|
contextPercent = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextPercent : null);
|
|
@@ -3199,6 +3235,12 @@ ${cssVarsBlock}
|
|
|
3199
3235
|
if (typeof message.contextTokens === "number") summary.contextTokens = message.contextTokens;
|
|
3200
3236
|
if (typeof message.contextWindow === "number") summary.contextWindow = message.contextWindow;
|
|
3201
3237
|
if (typeof message.contextPercent === "number") summary.contextPercent = message.contextPercent;
|
|
3238
|
+
if (typeof message.updateInstalledVersion === "string") summary.updateInstalledVersion = message.updateInstalledVersion;
|
|
3239
|
+
if (typeof message.updateLatestVersion === "string") summary.updateLatestVersion = message.updateLatestVersion;
|
|
3240
|
+
if (message.document && typeof message.document === "object" && typeof message.document.text === "string") {
|
|
3241
|
+
summary.documentLength = message.document.text.length;
|
|
3242
|
+
if (typeof message.document.label === "string") summary.documentLabel = message.document.label;
|
|
3243
|
+
}
|
|
3202
3244
|
if (typeof message.compactInProgress === "boolean") summary.compactInProgress = message.compactInProgress;
|
|
3203
3245
|
if (typeof message.stopReason === "string") summary.stopReason = message.stopReason;
|
|
3204
3246
|
if (typeof message.markdown === "string") summary.markdownLength = message.markdown.length;
|
|
@@ -3388,6 +3430,30 @@ ${cssVarsBlock}
|
|
|
3388
3430
|
return changed;
|
|
3389
3431
|
}
|
|
3390
3432
|
|
|
3433
|
+
function applyUpdateInfoFromMessage(message) {
|
|
3434
|
+
if (!message || typeof message !== "object") return false;
|
|
3435
|
+
|
|
3436
|
+
let changed = false;
|
|
3437
|
+
|
|
3438
|
+
if (Object.prototype.hasOwnProperty.call(message, "updateInstalledVersion")) {
|
|
3439
|
+
const nextInstalled = parseNonEmptyString(message.updateInstalledVersion);
|
|
3440
|
+
if (nextInstalled !== updateInstalledVersion) {
|
|
3441
|
+
updateInstalledVersion = nextInstalled;
|
|
3442
|
+
changed = true;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
if (Object.prototype.hasOwnProperty.call(message, "updateLatestVersion")) {
|
|
3447
|
+
const nextLatest = parseNonEmptyString(message.updateLatestVersion);
|
|
3448
|
+
if (nextLatest !== updateLatestVersion) {
|
|
3449
|
+
updateLatestVersion = nextLatest;
|
|
3450
|
+
changed = true;
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
return changed;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3391
3457
|
function updateDocumentTitle() {
|
|
3392
3458
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
3393
3459
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
@@ -3401,7 +3467,13 @@ ${cssVarsBlock}
|
|
|
3401
3467
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
3402
3468
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
3403
3469
|
const contextText = formatContextUsageText();
|
|
3404
|
-
|
|
3470
|
+
let updateText = "";
|
|
3471
|
+
if (updateLatestVersion) {
|
|
3472
|
+
updateText = updateInstalledVersion
|
|
3473
|
+
? "Update: " + updateInstalledVersion + " → " + updateLatestVersion
|
|
3474
|
+
: "Update: " + updateLatestVersion + " available";
|
|
3475
|
+
}
|
|
3476
|
+
const text = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextText + (updateText ? " · " + updateText : "");
|
|
3405
3477
|
if (footerMetaTextEl) {
|
|
3406
3478
|
footerMetaTextEl.textContent = text;
|
|
3407
3479
|
footerMetaTextEl.title = text;
|
|
@@ -3628,10 +3700,14 @@ ${cssVarsBlock}
|
|
|
3628
3700
|
const prompt = typeof item.prompt === "string"
|
|
3629
3701
|
? item.prompt
|
|
3630
3702
|
: (item.prompt == null ? null : String(item.prompt));
|
|
3703
|
+
const thinking = typeof item.thinking === "string"
|
|
3704
|
+
? item.thinking
|
|
3705
|
+
: (item.thinking == null ? null : String(item.thinking));
|
|
3631
3706
|
|
|
3632
3707
|
return {
|
|
3633
3708
|
id,
|
|
3634
3709
|
markdown,
|
|
3710
|
+
thinking,
|
|
3635
3711
|
timestamp,
|
|
3636
3712
|
kind: normalizeHistoryKind(item.kind),
|
|
3637
3713
|
prompt,
|
|
@@ -3646,11 +3722,13 @@ ${cssVarsBlock}
|
|
|
3646
3722
|
|
|
3647
3723
|
function clearActiveResponseView() {
|
|
3648
3724
|
latestResponseMarkdown = "";
|
|
3725
|
+
latestResponseThinking = "";
|
|
3649
3726
|
latestResponseKind = "annotation";
|
|
3650
3727
|
latestResponseTimestamp = 0;
|
|
3651
3728
|
latestResponseIsStructuredCritique = false;
|
|
3652
3729
|
latestResponseHasContent = false;
|
|
3653
3730
|
latestResponseNormalized = "";
|
|
3731
|
+
latestResponseThinkingNormalized = "";
|
|
3654
3732
|
latestCritiqueNotes = "";
|
|
3655
3733
|
latestCritiqueNotesNormalized = "";
|
|
3656
3734
|
refreshResponseUi();
|
|
@@ -3687,7 +3765,7 @@ ${cssVarsBlock}
|
|
|
3687
3765
|
clearActiveResponseView();
|
|
3688
3766
|
return false;
|
|
3689
3767
|
}
|
|
3690
|
-
handleIncomingResponse(item.markdown, item.kind, item.timestamp);
|
|
3768
|
+
handleIncomingResponse(item.markdown, item.kind, item.timestamp, item.thinking);
|
|
3691
3769
|
return true;
|
|
3692
3770
|
}
|
|
3693
3771
|
|
|
@@ -3770,6 +3848,26 @@ ${cssVarsBlock}
|
|
|
3770
3848
|
}
|
|
3771
3849
|
|
|
3772
3850
|
const hasResponse = Boolean(latestResponseMarkdown && latestResponseMarkdown.trim());
|
|
3851
|
+
const hasThinking = Boolean(latestResponseThinking && latestResponseThinking.trim());
|
|
3852
|
+
if (rightView === "thinking") {
|
|
3853
|
+
if (!hasResponse && !hasThinking) {
|
|
3854
|
+
referenceBadgeEl.textContent = "Thinking: none";
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
const time = formatReferenceTime(latestResponseTimestamp);
|
|
3859
|
+
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
3860
|
+
const selected = total > 0 && responseHistoryIndex >= 0 && responseHistoryIndex < total
|
|
3861
|
+
? responseHistoryIndex + 1
|
|
3862
|
+
: 0;
|
|
3863
|
+
const historyPrefix = total > 0 ? "Response history " + selected + "/" + total + " · " : "";
|
|
3864
|
+
const thinkingLabel = hasThinking ? "assistant thinking" : "assistant thinking unavailable";
|
|
3865
|
+
referenceBadgeEl.textContent = time
|
|
3866
|
+
? historyPrefix + thinkingLabel + " · " + time
|
|
3867
|
+
: historyPrefix + thinkingLabel;
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3773
3871
|
if (!hasResponse) {
|
|
3774
3872
|
referenceBadgeEl.textContent = "Latest response: none";
|
|
3775
3873
|
return;
|
|
@@ -3820,8 +3918,13 @@ ${cssVarsBlock}
|
|
|
3820
3918
|
function updateSyncBadge(normalizedEditorText) {
|
|
3821
3919
|
if (!syncBadgeEl) return;
|
|
3822
3920
|
|
|
3823
|
-
|
|
3824
|
-
|
|
3921
|
+
const showingThinking = rightView === "thinking";
|
|
3922
|
+
const hasComparableContent = showingThinking
|
|
3923
|
+
? Boolean(latestResponseThinking && latestResponseThinking.trim())
|
|
3924
|
+
: latestResponseHasContent;
|
|
3925
|
+
|
|
3926
|
+
if (!hasComparableContent) {
|
|
3927
|
+
syncBadgeEl.textContent = showingThinking ? "No thinking loaded" : "No response loaded";
|
|
3825
3928
|
syncBadgeEl.classList.remove("sync", "edited");
|
|
3826
3929
|
return;
|
|
3827
3930
|
}
|
|
@@ -3829,13 +3932,14 @@ ${cssVarsBlock}
|
|
|
3829
3932
|
const normalizedEditor = typeof normalizedEditorText === "string"
|
|
3830
3933
|
? normalizedEditorText
|
|
3831
3934
|
: normalizeForCompare(sourceTextEl.value);
|
|
3832
|
-
const
|
|
3935
|
+
const targetNormalized = showingThinking ? latestResponseThinkingNormalized : latestResponseNormalized;
|
|
3936
|
+
const inSync = normalizedEditor === targetNormalized;
|
|
3833
3937
|
if (inSync) {
|
|
3834
|
-
syncBadgeEl.textContent = "In sync with response";
|
|
3938
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
3835
3939
|
syncBadgeEl.classList.add("sync");
|
|
3836
3940
|
syncBadgeEl.classList.remove("edited");
|
|
3837
3941
|
} else {
|
|
3838
|
-
syncBadgeEl.textContent = "Out of sync with response";
|
|
3942
|
+
syncBadgeEl.textContent = showingThinking ? "Out of sync with thinking" : "Out of sync with response";
|
|
3839
3943
|
syncBadgeEl.classList.add("edited");
|
|
3840
3944
|
syncBadgeEl.classList.remove("sync");
|
|
3841
3945
|
}
|
|
@@ -4367,6 +4471,15 @@ ${cssVarsBlock}
|
|
|
4367
4471
|
return;
|
|
4368
4472
|
}
|
|
4369
4473
|
|
|
4474
|
+
if (rightView === "thinking") {
|
|
4475
|
+
const thinking = latestResponseThinking;
|
|
4476
|
+
finishPreviewRender(critiqueViewEl);
|
|
4477
|
+
critiqueViewEl.innerHTML = thinking && thinking.trim()
|
|
4478
|
+
? buildPlainMarkdownHtml(thinking)
|
|
4479
|
+
: "<pre class='plain-markdown'>No thinking available for this response.</pre>";
|
|
4480
|
+
return;
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4370
4483
|
const markdown = latestResponseMarkdown;
|
|
4371
4484
|
if (!markdown || !markdown.trim()) {
|
|
4372
4485
|
finishPreviewRender(critiqueViewEl);
|
|
@@ -4402,36 +4515,56 @@ ${cssVarsBlock}
|
|
|
4402
4515
|
|
|
4403
4516
|
function updateResultActionButtons(normalizedEditorText) {
|
|
4404
4517
|
const hasResponse = latestResponseHasContent;
|
|
4518
|
+
const hasThinking = Boolean(latestResponseThinking && latestResponseThinking.trim());
|
|
4405
4519
|
const normalizedEditor = typeof normalizedEditorText === "string"
|
|
4406
4520
|
? normalizedEditorText
|
|
4407
4521
|
: normalizeForCompare(sourceTextEl.value);
|
|
4408
4522
|
const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
|
|
4523
|
+
const thinkingLoaded = hasThinking && normalizedEditor === latestResponseThinkingNormalized;
|
|
4409
4524
|
const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
|
|
4525
|
+
const showingThinking = rightView === "thinking";
|
|
4410
4526
|
|
|
4411
4527
|
const critiqueNotes = isCritiqueResponse ? latestCritiqueNotes : "";
|
|
4412
4528
|
const critiqueNotesLoaded = Boolean(critiqueNotes) && normalizedEditor === latestCritiqueNotesNormalized;
|
|
4413
4529
|
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4530
|
+
if (showingThinking) {
|
|
4531
|
+
loadResponseBtn.hidden = false;
|
|
4532
|
+
loadCritiqueNotesBtn.hidden = true;
|
|
4533
|
+
loadCritiqueFullBtn.hidden = true;
|
|
4417
4534
|
|
|
4418
|
-
|
|
4419
|
-
|
|
4535
|
+
loadResponseBtn.disabled = uiBusy || !hasThinking || thinkingLoaded;
|
|
4536
|
+
loadResponseBtn.textContent = !hasThinking
|
|
4537
|
+
? "Thinking unavailable"
|
|
4538
|
+
: (thinkingLoaded ? "Thinking already in editor" : "Load thinking into editor");
|
|
4420
4539
|
|
|
4421
|
-
|
|
4422
|
-
|
|
4540
|
+
copyResponseBtn.disabled = uiBusy || !hasThinking;
|
|
4541
|
+
copyResponseBtn.textContent = "Copy thinking text";
|
|
4542
|
+
} else {
|
|
4543
|
+
loadResponseBtn.hidden = isCritiqueResponse;
|
|
4544
|
+
loadCritiqueNotesBtn.hidden = !isCritiqueResponse;
|
|
4545
|
+
loadCritiqueFullBtn.hidden = !isCritiqueResponse;
|
|
4423
4546
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4547
|
+
loadResponseBtn.disabled = uiBusy || !hasResponse || responseLoaded || isCritiqueResponse;
|
|
4548
|
+
loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
|
|
4426
4549
|
|
|
4427
|
-
|
|
4550
|
+
loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
|
|
4551
|
+
loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
|
|
4552
|
+
|
|
4553
|
+
loadCritiqueFullBtn.disabled = uiBusy || !isCritiqueResponse || responseLoaded;
|
|
4554
|
+
loadCritiqueFullBtn.textContent = responseLoaded ? "Full critique already in editor" : "Load full critique into editor";
|
|
4555
|
+
|
|
4556
|
+
copyResponseBtn.disabled = uiBusy || !hasResponse;
|
|
4557
|
+
copyResponseBtn.textContent = "Copy response text";
|
|
4558
|
+
}
|
|
4428
4559
|
|
|
4429
4560
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
4430
4561
|
const exportText = rightView === "editor-preview" ? prepareEditorTextForPreview(sourceTextEl.value) : latestResponseMarkdown;
|
|
4431
4562
|
const canExportPdf = rightPaneShowsPreview && Boolean(String(exportText || "").trim());
|
|
4432
4563
|
if (exportPdfBtn) {
|
|
4433
4564
|
exportPdfBtn.disabled = uiBusy || pdfExportInProgress || !canExportPdf;
|
|
4434
|
-
if (rightView === "
|
|
4565
|
+
if (rightView === "thinking") {
|
|
4566
|
+
exportPdfBtn.title = "Thinking view does not support PDF export yet.";
|
|
4567
|
+
} else if (rightView === "markdown") {
|
|
4435
4568
|
exportPdfBtn.title = "Switch right pane to Response (Preview) or Editor (Preview) to export PDF.";
|
|
4436
4569
|
} else if (!canExportPdf) {
|
|
4437
4570
|
exportPdfBtn.title = "Nothing to export yet.";
|
|
@@ -4609,7 +4742,11 @@ ${cssVarsBlock}
|
|
|
4609
4742
|
}
|
|
4610
4743
|
|
|
4611
4744
|
function setRightView(nextView) {
|
|
4612
|
-
rightView = nextView === "preview"
|
|
4745
|
+
rightView = nextView === "preview"
|
|
4746
|
+
? "preview"
|
|
4747
|
+
: (nextView === "editor-preview"
|
|
4748
|
+
? "editor-preview"
|
|
4749
|
+
: (nextView === "thinking" ? "thinking" : "markdown"));
|
|
4613
4750
|
rightViewSelect.value = rightView;
|
|
4614
4751
|
|
|
4615
4752
|
if (rightView !== "editor-preview" && responseEditorPreviewTimer) {
|
|
@@ -5286,18 +5423,20 @@ ${cssVarsBlock}
|
|
|
5286
5423
|
return lower.indexOf("## critiques") !== -1 && lower.indexOf("## document") !== -1;
|
|
5287
5424
|
}
|
|
5288
5425
|
|
|
5289
|
-
function handleIncomingResponse(markdown, kind, timestamp) {
|
|
5426
|
+
function handleIncomingResponse(markdown, kind, timestamp, thinking) {
|
|
5290
5427
|
const responseTimestamp =
|
|
5291
5428
|
typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp > 0
|
|
5292
5429
|
? timestamp
|
|
5293
5430
|
: Date.now();
|
|
5294
5431
|
|
|
5295
5432
|
latestResponseMarkdown = markdown;
|
|
5433
|
+
latestResponseThinking = typeof thinking === "string" ? thinking : "";
|
|
5296
5434
|
latestResponseKind = kind === "critique" ? "critique" : "annotation";
|
|
5297
5435
|
latestResponseTimestamp = responseTimestamp;
|
|
5298
5436
|
latestResponseIsStructuredCritique = isStructuredCritique(markdown);
|
|
5299
5437
|
latestResponseHasContent = Boolean(markdown && markdown.trim());
|
|
5300
5438
|
latestResponseNormalized = normalizeForCompare(markdown);
|
|
5439
|
+
latestResponseThinkingNormalized = normalizeForCompare(latestResponseThinking);
|
|
5301
5440
|
|
|
5302
5441
|
if (latestResponseIsStructuredCritique) {
|
|
5303
5442
|
latestCritiqueNotes = buildCritiqueNotesMarkdown(markdown);
|
|
@@ -5313,7 +5452,7 @@ ${cssVarsBlock}
|
|
|
5313
5452
|
function applyLatestPayload(payload) {
|
|
5314
5453
|
if (!payload || typeof payload.markdown !== "string") return false;
|
|
5315
5454
|
const responseKind = payload.kind === "critique" ? "critique" : "annotation";
|
|
5316
|
-
handleIncomingResponse(payload.markdown, responseKind, payload.timestamp);
|
|
5455
|
+
handleIncomingResponse(payload.markdown, responseKind, payload.timestamp, payload.thinking);
|
|
5317
5456
|
return true;
|
|
5318
5457
|
}
|
|
5319
5458
|
|
|
@@ -5332,7 +5471,9 @@ ${cssVarsBlock}
|
|
|
5332
5471
|
|
|
5333
5472
|
debugTrace("server_message", summarizeServerMessage(message));
|
|
5334
5473
|
|
|
5335
|
-
|
|
5474
|
+
const contextChanged = applyContextUsageFromMessage(message);
|
|
5475
|
+
const updateInfoChanged = applyUpdateInfoFromMessage(message);
|
|
5476
|
+
if (contextChanged || updateInfoChanged) {
|
|
5336
5477
|
updateFooterMeta();
|
|
5337
5478
|
}
|
|
5338
5479
|
|
|
@@ -5410,7 +5551,7 @@ ${cssVarsBlock}
|
|
|
5410
5551
|
message.lastResponse.kind === "critique"
|
|
5411
5552
|
? "critique"
|
|
5412
5553
|
: (isStructuredCritique(lastMarkdown) ? "critique" : "annotation");
|
|
5413
|
-
handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp);
|
|
5554
|
+
handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp, message.lastResponse.thinking);
|
|
5414
5555
|
}
|
|
5415
5556
|
|
|
5416
5557
|
if (pendingRequestId) {
|
|
@@ -5507,7 +5648,7 @@ ${cssVarsBlock}
|
|
|
5507
5648
|
}
|
|
5508
5649
|
|
|
5509
5650
|
if (!appliedFromHistory && typeof message.markdown === "string") {
|
|
5510
|
-
handleIncomingResponse(message.markdown, responseKind, message.timestamp);
|
|
5651
|
+
handleIncomingResponse(message.markdown, responseKind, message.timestamp, message.thinking);
|
|
5511
5652
|
}
|
|
5512
5653
|
|
|
5513
5654
|
if (responseKind === "critique") {
|
|
@@ -5536,6 +5677,7 @@ ${cssVarsBlock}
|
|
|
5536
5677
|
const payload = {
|
|
5537
5678
|
kind: message.kind === "critique" ? "critique" : "annotation",
|
|
5538
5679
|
markdown: message.markdown,
|
|
5680
|
+
thinking: typeof message.thinking === "string" ? message.thinking : null,
|
|
5539
5681
|
timestamp: message.timestamp,
|
|
5540
5682
|
};
|
|
5541
5683
|
|
|
@@ -5621,6 +5763,35 @@ ${cssVarsBlock}
|
|
|
5621
5763
|
return;
|
|
5622
5764
|
}
|
|
5623
5765
|
|
|
5766
|
+
if (message.type === "studio_document") {
|
|
5767
|
+
const nextDoc = message.document;
|
|
5768
|
+
if (!nextDoc || typeof nextDoc !== "object" || typeof nextDoc.text !== "string") {
|
|
5769
|
+
return;
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
const nextSource =
|
|
5773
|
+
nextDoc.source === "file" || nextDoc.source === "last-response"
|
|
5774
|
+
? nextDoc.source
|
|
5775
|
+
: "blank";
|
|
5776
|
+
const nextLabel = typeof nextDoc.label === "string" && nextDoc.label.trim()
|
|
5777
|
+
? nextDoc.label.trim()
|
|
5778
|
+
: (nextSource === "file" ? "file" : "studio document");
|
|
5779
|
+
const nextPath = typeof nextDoc.path === "string" && nextDoc.path.trim()
|
|
5780
|
+
? nextDoc.path
|
|
5781
|
+
: null;
|
|
5782
|
+
|
|
5783
|
+
setEditorText(nextDoc.text, { preserveScroll: false, preserveSelection: false });
|
|
5784
|
+
setSourceState({ source: nextSource, label: nextLabel, path: nextPath });
|
|
5785
|
+
refreshResponseUi();
|
|
5786
|
+
setStatus(
|
|
5787
|
+
typeof message.message === "string" && message.message.trim()
|
|
5788
|
+
? message.message
|
|
5789
|
+
: "Loaded document from terminal.",
|
|
5790
|
+
"success",
|
|
5791
|
+
);
|
|
5792
|
+
return;
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5624
5795
|
if (message.type === "studio_state") {
|
|
5625
5796
|
const busy = Boolean(message.busy);
|
|
5626
5797
|
agentBusyFromServer = Boolean(message.agentBusy);
|
|
@@ -6097,6 +6268,17 @@ ${cssVarsBlock}
|
|
|
6097
6268
|
});
|
|
6098
6269
|
|
|
6099
6270
|
loadResponseBtn.addEventListener("click", () => {
|
|
6271
|
+
if (rightView === "thinking") {
|
|
6272
|
+
if (!latestResponseThinking.trim()) {
|
|
6273
|
+
setStatus("No thinking available for the selected response.", "warning");
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
setEditorText(latestResponseThinking, { preserveScroll: false, preserveSelection: false });
|
|
6277
|
+
setSourceState({ source: "blank", label: "assistant thinking", path: null });
|
|
6278
|
+
setStatus("Loaded thinking into editor.", "success");
|
|
6279
|
+
return;
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6100
6282
|
if (!latestResponseMarkdown.trim()) {
|
|
6101
6283
|
setStatus("No response available yet.", "warning");
|
|
6102
6284
|
return;
|
|
@@ -6135,14 +6317,15 @@ ${cssVarsBlock}
|
|
|
6135
6317
|
});
|
|
6136
6318
|
|
|
6137
6319
|
copyResponseBtn.addEventListener("click", async () => {
|
|
6138
|
-
|
|
6139
|
-
|
|
6320
|
+
const content = rightView === "thinking" ? latestResponseThinking : latestResponseMarkdown;
|
|
6321
|
+
if (!content.trim()) {
|
|
6322
|
+
setStatus(rightView === "thinking" ? "No thinking available yet." : "No response available yet.", "warning");
|
|
6140
6323
|
return;
|
|
6141
6324
|
}
|
|
6142
6325
|
|
|
6143
6326
|
try {
|
|
6144
|
-
await navigator.clipboard.writeText(
|
|
6145
|
-
setStatus("Copied response text.", "success");
|
|
6327
|
+
await navigator.clipboard.writeText(content);
|
|
6328
|
+
setStatus(rightView === "thinking" ? "Copied thinking text." : "Copied response text.", "success");
|
|
6146
6329
|
} catch (error) {
|
|
6147
6330
|
setStatus("Clipboard write failed.", "warning");
|
|
6148
6331
|
}
|
|
@@ -6487,6 +6670,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6487
6670
|
let updateCheckStarted = false;
|
|
6488
6671
|
let updateCheckCompleted = false;
|
|
6489
6672
|
const packageMetadata = readLocalPackageMetadata();
|
|
6673
|
+
const installedPackageVersion = packageMetadata?.version ?? null;
|
|
6674
|
+
let updateAvailableLatestVersion: string | null = null;
|
|
6490
6675
|
|
|
6491
6676
|
const isStudioBusy = () => agentBusy || activeRequest !== null || compactInProgress;
|
|
6492
6677
|
|
|
@@ -6558,6 +6743,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6558
6743
|
}
|
|
6559
6744
|
lastStudioResponse = {
|
|
6560
6745
|
markdown: latest.markdown,
|
|
6746
|
+
thinking: latest.thinking,
|
|
6561
6747
|
timestamp: latest.timestamp,
|
|
6562
6748
|
kind: latest.kind,
|
|
6563
6749
|
};
|
|
@@ -6579,10 +6765,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
6579
6765
|
const latest = await fetchLatestNpmVersion(metadata.name, UPDATE_CHECK_TIMEOUT_MS);
|
|
6580
6766
|
if (!latest) return;
|
|
6581
6767
|
if (!isVersionBehind(metadata.version, latest)) return;
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6768
|
+
|
|
6769
|
+
updateAvailableLatestVersion = latest;
|
|
6770
|
+
broadcastState();
|
|
6771
|
+
|
|
6772
|
+
const notification =
|
|
6773
|
+
`Update available for ${metadata.name}: ${metadata.version} → ${latest}. Run: pi install npm:${metadata.name}`;
|
|
6774
|
+
ctx.ui.notify(notification, "info");
|
|
6775
|
+
broadcast({ type: "info", message: notification, level: "info" });
|
|
6586
6776
|
} finally {
|
|
6587
6777
|
updateCheckCompleted = true;
|
|
6588
6778
|
}
|
|
@@ -6689,6 +6879,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6689
6879
|
contextTokens: contextUsageSnapshot.tokens,
|
|
6690
6880
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
6691
6881
|
contextPercent: contextUsageSnapshot.percent,
|
|
6882
|
+
updateInstalledVersion: installedPackageVersion,
|
|
6883
|
+
updateLatestVersion: updateAvailableLatestVersion,
|
|
6692
6884
|
compactInProgress,
|
|
6693
6885
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
6694
6886
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
@@ -6793,6 +6985,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
6793
6985
|
contextTokens: contextUsageSnapshot.tokens,
|
|
6794
6986
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
6795
6987
|
contextPercent: contextUsageSnapshot.percent,
|
|
6988
|
+
updateInstalledVersion: installedPackageVersion,
|
|
6989
|
+
updateLatestVersion: updateAvailableLatestVersion,
|
|
6796
6990
|
compactInProgress,
|
|
6797
6991
|
activeRequestId: activeRequest?.id ?? compactRequestId ?? null,
|
|
6798
6992
|
activeRequestKind: activeRequest?.kind ?? (compactInProgress ? "compact" : null),
|
|
@@ -6812,6 +7006,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6812
7006
|
type: "latest_response",
|
|
6813
7007
|
kind: lastStudioResponse.kind,
|
|
6814
7008
|
markdown: lastStudioResponse.markdown,
|
|
7009
|
+
thinking: lastStudioResponse.thinking,
|
|
6815
7010
|
timestamp: lastStudioResponse.timestamp,
|
|
6816
7011
|
responseHistory: studioResponseHistory,
|
|
6817
7012
|
});
|
|
@@ -7424,7 +7619,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7424
7619
|
|
|
7425
7620
|
wsServer.on("connection", (ws) => {
|
|
7426
7621
|
clients.add(ws);
|
|
7427
|
-
|
|
7622
|
+
emitDebugEvent("studio_ws_connected", { clients: clients.size });
|
|
7428
7623
|
broadcastState();
|
|
7429
7624
|
|
|
7430
7625
|
ws.on("message", (data) => {
|
|
@@ -7438,7 +7633,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7438
7633
|
|
|
7439
7634
|
ws.on("close", () => {
|
|
7440
7635
|
clients.delete(ws);
|
|
7441
|
-
|
|
7636
|
+
emitDebugEvent("studio_ws_disconnected", { clients: clients.size });
|
|
7442
7637
|
});
|
|
7443
7638
|
|
|
7444
7639
|
ws.on("error", () => {
|
|
@@ -7626,11 +7821,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
7626
7821
|
const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
|
|
7627
7822
|
const role = typeof message.role === "string" ? message.role : "";
|
|
7628
7823
|
const markdown = extractAssistantText(event.message);
|
|
7824
|
+
const thinking = extractAssistantThinking(event.message);
|
|
7629
7825
|
emitDebugEvent("message_end", {
|
|
7630
7826
|
role,
|
|
7631
7827
|
stopReason,
|
|
7632
7828
|
hasMarkdown: Boolean(markdown),
|
|
7633
7829
|
markdownLength: markdown ? markdown.length : 0,
|
|
7830
|
+
hasThinking: Boolean(thinking),
|
|
7831
|
+
thinkingLength: thinking ? thinking.length : 0,
|
|
7634
7832
|
activeRequestId: activeRequest?.id ?? null,
|
|
7635
7833
|
activeRequestKind: activeRequest?.kind ?? null,
|
|
7636
7834
|
});
|
|
@@ -7657,6 +7855,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7657
7855
|
const fallbackHistoryItem: StudioResponseHistoryItem = {
|
|
7658
7856
|
id: randomUUID(),
|
|
7659
7857
|
markdown,
|
|
7858
|
+
thinking,
|
|
7660
7859
|
timestamp: Date.now(),
|
|
7661
7860
|
kind: inferStudioResponseKind(markdown),
|
|
7662
7861
|
prompt: fallbackPrompt,
|
|
@@ -7667,12 +7866,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
7667
7866
|
|
|
7668
7867
|
const latestItem = studioResponseHistory[studioResponseHistory.length - 1];
|
|
7669
7868
|
const responseTimestamp = latestItem?.timestamp ?? Date.now();
|
|
7869
|
+
const responseThinking = latestItem?.thinking ?? thinking ?? null;
|
|
7670
7870
|
|
|
7671
7871
|
if (activeRequest) {
|
|
7672
7872
|
const requestId = activeRequest.id;
|
|
7673
7873
|
const kind = activeRequest.kind;
|
|
7674
7874
|
lastStudioResponse = {
|
|
7675
7875
|
markdown,
|
|
7876
|
+
thinking: responseThinking,
|
|
7676
7877
|
timestamp: responseTimestamp,
|
|
7677
7878
|
kind,
|
|
7678
7879
|
};
|
|
@@ -7680,6 +7881,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7680
7881
|
requestId,
|
|
7681
7882
|
kind,
|
|
7682
7883
|
markdownLength: markdown.length,
|
|
7884
|
+
thinkingLength: responseThinking ? responseThinking.length : 0,
|
|
7683
7885
|
stopReason,
|
|
7684
7886
|
});
|
|
7685
7887
|
broadcast({
|
|
@@ -7687,6 +7889,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7687
7889
|
requestId,
|
|
7688
7890
|
kind,
|
|
7689
7891
|
markdown,
|
|
7892
|
+
thinking: lastStudioResponse.thinking,
|
|
7690
7893
|
timestamp: lastStudioResponse.timestamp,
|
|
7691
7894
|
responseHistory: studioResponseHistory,
|
|
7692
7895
|
});
|
|
@@ -7698,18 +7901,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
7698
7901
|
const inferredKind = inferStudioResponseKind(markdown);
|
|
7699
7902
|
lastStudioResponse = {
|
|
7700
7903
|
markdown,
|
|
7904
|
+
thinking: responseThinking,
|
|
7701
7905
|
timestamp: responseTimestamp,
|
|
7702
7906
|
kind: inferredKind,
|
|
7703
7907
|
};
|
|
7704
7908
|
emitDebugEvent("broadcast_latest_response", {
|
|
7705
7909
|
kind: inferredKind,
|
|
7706
7910
|
markdownLength: markdown.length,
|
|
7911
|
+
thinkingLength: responseThinking ? responseThinking.length : 0,
|
|
7707
7912
|
stopReason,
|
|
7708
7913
|
});
|
|
7709
7914
|
broadcast({
|
|
7710
7915
|
type: "latest_response",
|
|
7711
7916
|
kind: inferredKind,
|
|
7712
7917
|
markdown,
|
|
7918
|
+
thinking: lastStudioResponse.thinking,
|
|
7713
7919
|
timestamp: lastStudioResponse.timestamp,
|
|
7714
7920
|
responseHistory: studioResponseHistory,
|
|
7715
7921
|
});
|
|
@@ -7771,7 +7977,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
7771
7977
|
+ " /studio --blank Open with blank editor\n"
|
|
7772
7978
|
+ " /studio --last Open with last model response\n"
|
|
7773
7979
|
+ " /studio --status Show studio status\n"
|
|
7774
|
-
+ " /studio --stop Stop studio server"
|
|
7980
|
+
+ " /studio --stop Stop studio server\n"
|
|
7981
|
+
+ " /studio-current <path> Load a file into currently open Studio tab(s)",
|
|
7775
7982
|
"info",
|
|
7776
7983
|
);
|
|
7777
7984
|
return;
|
|
@@ -7784,7 +7991,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
7784
7991
|
syncStudioResponseHistory(ctx.sessionManager.getBranch());
|
|
7785
7992
|
broadcastState();
|
|
7786
7993
|
broadcastResponseHistory();
|
|
7787
|
-
void maybeNotifyUpdateAvailable(ctx);
|
|
7788
7994
|
// Seed theme vars so first ping doesn't trigger a false update
|
|
7789
7995
|
try {
|
|
7790
7996
|
const currentStyle = getStudioThemeStyle(ctx.ui.theme);
|
|
@@ -7884,7 +8090,71 @@ export default function (pi: ExtensionAPI) {
|
|
|
7884
8090
|
ctx.ui.notify(`Studio URL: ${url}`, "info");
|
|
7885
8091
|
} catch (error) {
|
|
7886
8092
|
ctx.ui.notify(`Failed to open browser: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
8093
|
+
} finally {
|
|
8094
|
+
void maybeNotifyUpdateAvailable(ctx);
|
|
8095
|
+
}
|
|
8096
|
+
},
|
|
8097
|
+
});
|
|
8098
|
+
|
|
8099
|
+
pi.registerCommand("studio-current", {
|
|
8100
|
+
description: "Load a file into current open Studio tab(s) without opening a new browser session",
|
|
8101
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
8102
|
+
const trimmed = args.trim();
|
|
8103
|
+
if (!trimmed || trimmed === "help" || trimmed === "--help" || trimmed === "-h") {
|
|
8104
|
+
ctx.ui.notify(
|
|
8105
|
+
"Usage: /studio-current <path>\n"
|
|
8106
|
+
+ " Load a file into currently open Studio tab(s) without opening a new browser window.",
|
|
8107
|
+
"info",
|
|
8108
|
+
);
|
|
8109
|
+
return;
|
|
8110
|
+
}
|
|
8111
|
+
|
|
8112
|
+
const pathArg = parsePathArgument(trimmed);
|
|
8113
|
+
if (!pathArg) {
|
|
8114
|
+
ctx.ui.notify("Invalid file path argument.", "error");
|
|
8115
|
+
return;
|
|
8116
|
+
}
|
|
8117
|
+
|
|
8118
|
+
const file = readStudioFile(pathArg, ctx.cwd);
|
|
8119
|
+
if (!file.ok) {
|
|
8120
|
+
ctx.ui.notify(file.message, "error");
|
|
8121
|
+
return;
|
|
8122
|
+
}
|
|
8123
|
+
|
|
8124
|
+
if (!serverState || serverState.clients.size === 0) {
|
|
8125
|
+
ctx.ui.notify("No open Studio tab is connected. Run /studio first.", "warning");
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
|
|
8129
|
+
await ctx.waitForIdle();
|
|
8130
|
+
lastCommandCtx = ctx;
|
|
8131
|
+
refreshRuntimeMetadata({ cwd: ctx.cwd, model: ctx.model });
|
|
8132
|
+
refreshContextUsage(ctx);
|
|
8133
|
+
syncStudioResponseHistory(ctx.sessionManager.getBranch());
|
|
8134
|
+
|
|
8135
|
+
const nextDoc: InitialStudioDocument = {
|
|
8136
|
+
text: file.text,
|
|
8137
|
+
label: file.label,
|
|
8138
|
+
source: "file",
|
|
8139
|
+
path: file.resolvedPath,
|
|
8140
|
+
};
|
|
8141
|
+
initialStudioDocument = nextDoc;
|
|
8142
|
+
|
|
8143
|
+
broadcastState();
|
|
8144
|
+
broadcastResponseHistory();
|
|
8145
|
+
broadcast({
|
|
8146
|
+
type: "studio_document",
|
|
8147
|
+
document: nextDoc,
|
|
8148
|
+
message: `Loaded ${file.label} from terminal command.`,
|
|
8149
|
+
});
|
|
8150
|
+
|
|
8151
|
+
if (file.text.length > 200_000) {
|
|
8152
|
+
ctx.ui.notify(
|
|
8153
|
+
"Loaded a large file into Studio. Critique requests currently reject documents over 200k characters.",
|
|
8154
|
+
"warning",
|
|
8155
|
+
);
|
|
7887
8156
|
}
|
|
8157
|
+
ctx.ui.notify(`Loaded file into open Studio tab(s): ${file.label}`, "info");
|
|
7888
8158
|
},
|
|
7889
8159
|
});
|
|
7890
8160
|
}
|