pi-studio 0.5.3 → 0.5.5

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 CHANGED
@@ -88,6 +88,27 @@ All notable changes to `pi-studio` are documented here.
88
88
 
89
89
  ## [Unreleased]
90
90
 
91
+ ## [0.5.5] — 2026-03-09
92
+
93
+ ### Fixed
94
+ - Improved raw-editor caret/overlay alignment in Syntax highlight mode:
95
+ - width-neutral annotation highlight styling
96
+ - more textarea-like wrap behavior in the highlight overlay
97
+ - preserved empty trailing lines in highlighted output so end-of-file blank lines stay aligned
98
+ - reduced raw overlay metric drift for comment/quote styling
99
+
100
+ ## [0.5.4] — 2026-03-09
101
+
102
+ ### Added
103
+ - New right-pane **Thinking (Raw)** view for assistant/model thinking when available.
104
+
105
+ ### Changed
106
+ - Response history and latest-response syncing now preserve associated thinking content.
107
+ - In Thinking view, right-pane actions adapt to the selected reasoning trace:
108
+ - **Load thinking into editor**
109
+ - **Copy thinking text**
110
+ - thinking-aware reference/sync badges
111
+
91
112
  ## [0.5.3] — 2026-03-06
92
113
 
93
114
  ### Added
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,
@@ -2140,6 +2165,7 @@ ${cssVarsBlock}
2140
2165
  border-radius: 8px;
2141
2166
  background: var(--editor-bg);
2142
2167
  overflow: hidden;
2168
+ overscroll-behavior: none;
2143
2169
  }
2144
2170
 
2145
2171
  .editor-highlight {
@@ -2152,7 +2178,9 @@ ${cssVarsBlock}
2152
2178
  overflow: auto;
2153
2179
  pointer-events: none;
2154
2180
  white-space: pre-wrap;
2155
- word-break: break-word;
2181
+ word-break: normal;
2182
+ overflow-wrap: break-word;
2183
+ overscroll-behavior: none;
2156
2184
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
2157
2185
  font-size: 13px;
2158
2186
  line-height: 1.45;
@@ -2172,6 +2200,7 @@ ${cssVarsBlock}
2172
2200
  background: transparent;
2173
2201
  resize: none;
2174
2202
  outline: none;
2203
+ overscroll-behavior: none;
2175
2204
  }
2176
2205
 
2177
2206
  #sourceText.highlight-active {
@@ -2215,7 +2244,7 @@ ${cssVarsBlock}
2215
2244
 
2216
2245
  .hl-code-com {
2217
2246
  color: var(--syntax-comment);
2218
- font-style: italic;
2247
+ font-style: normal;
2219
2248
  }
2220
2249
 
2221
2250
  .hl-code-var,
@@ -2244,7 +2273,7 @@ ${cssVarsBlock}
2244
2273
 
2245
2274
  .hl-quote {
2246
2275
  color: var(--md-quote);
2247
- font-style: italic;
2276
+ font-style: normal;
2248
2277
  }
2249
2278
 
2250
2279
  .hl-link {
@@ -2259,9 +2288,10 @@ ${cssVarsBlock}
2259
2288
  .hl-annotation {
2260
2289
  color: var(--accent);
2261
2290
  background: var(--accent-soft);
2262
- border: 1px solid var(--marker-border);
2291
+ border: 0;
2263
2292
  border-radius: 4px;
2264
- padding: 0 3px;
2293
+ padding: 0;
2294
+ box-shadow: inset 0 0 0 1px var(--marker-border);
2265
2295
  }
2266
2296
 
2267
2297
  .hl-annotation-muted {
@@ -2889,6 +2919,7 @@ ${cssVarsBlock}
2889
2919
  <option value="markdown">Response (Raw)</option>
2890
2920
  <option value="preview" selected>Response (Preview)</option>
2891
2921
  <option value="editor-preview">Editor (Preview)</option>
2922
+ <option value="thinking">Thinking (Raw)</option>
2892
2923
  </select>
2893
2924
  </div>
2894
2925
  <div class="section-header-actions">
@@ -3043,11 +3074,13 @@ ${cssVarsBlock}
3043
3074
  let followLatest = true;
3044
3075
  let queuedLatestResponse = null;
3045
3076
  let latestResponseMarkdown = "";
3077
+ let latestResponseThinking = "";
3046
3078
  let latestResponseTimestamp = 0;
3047
3079
  let latestResponseKind = "annotation";
3048
3080
  let latestResponseIsStructuredCritique = false;
3049
3081
  let latestResponseHasContent = false;
3050
3082
  let latestResponseNormalized = "";
3083
+ let latestResponseThinkingNormalized = "";
3051
3084
  let latestCritiqueNotes = "";
3052
3085
  let latestCritiqueNotesNormalized = "";
3053
3086
  let responseHistory = [];
@@ -3149,6 +3182,7 @@ ${cssVarsBlock}
3149
3182
  let editorHighlightRenderRaf = null;
3150
3183
  let annotationsEnabled = true;
3151
3184
  const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]\\n]+?)\\]/gi;
3185
+ const EMPTY_OVERLAY_LINE = "\\u200b";
3152
3186
  const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
3153
3187
  const MERMAID_CONFIG = ${JSON.stringify(mermaidConfig)};
3154
3188
  const MERMAID_UNAVAILABLE_MESSAGE = "Mermaid renderer unavailable. Showing mermaid blocks as code.";
@@ -3672,10 +3706,14 @@ ${cssVarsBlock}
3672
3706
  const prompt = typeof item.prompt === "string"
3673
3707
  ? item.prompt
3674
3708
  : (item.prompt == null ? null : String(item.prompt));
3709
+ const thinking = typeof item.thinking === "string"
3710
+ ? item.thinking
3711
+ : (item.thinking == null ? null : String(item.thinking));
3675
3712
 
3676
3713
  return {
3677
3714
  id,
3678
3715
  markdown,
3716
+ thinking,
3679
3717
  timestamp,
3680
3718
  kind: normalizeHistoryKind(item.kind),
3681
3719
  prompt,
@@ -3690,11 +3728,13 @@ ${cssVarsBlock}
3690
3728
 
3691
3729
  function clearActiveResponseView() {
3692
3730
  latestResponseMarkdown = "";
3731
+ latestResponseThinking = "";
3693
3732
  latestResponseKind = "annotation";
3694
3733
  latestResponseTimestamp = 0;
3695
3734
  latestResponseIsStructuredCritique = false;
3696
3735
  latestResponseHasContent = false;
3697
3736
  latestResponseNormalized = "";
3737
+ latestResponseThinkingNormalized = "";
3698
3738
  latestCritiqueNotes = "";
3699
3739
  latestCritiqueNotesNormalized = "";
3700
3740
  refreshResponseUi();
@@ -3731,7 +3771,7 @@ ${cssVarsBlock}
3731
3771
  clearActiveResponseView();
3732
3772
  return false;
3733
3773
  }
3734
- handleIncomingResponse(item.markdown, item.kind, item.timestamp);
3774
+ handleIncomingResponse(item.markdown, item.kind, item.timestamp, item.thinking);
3735
3775
  return true;
3736
3776
  }
3737
3777
 
@@ -3814,6 +3854,26 @@ ${cssVarsBlock}
3814
3854
  }
3815
3855
 
3816
3856
  const hasResponse = Boolean(latestResponseMarkdown && latestResponseMarkdown.trim());
3857
+ const hasThinking = Boolean(latestResponseThinking && latestResponseThinking.trim());
3858
+ if (rightView === "thinking") {
3859
+ if (!hasResponse && !hasThinking) {
3860
+ referenceBadgeEl.textContent = "Thinking: none";
3861
+ return;
3862
+ }
3863
+
3864
+ const time = formatReferenceTime(latestResponseTimestamp);
3865
+ const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
3866
+ const selected = total > 0 && responseHistoryIndex >= 0 && responseHistoryIndex < total
3867
+ ? responseHistoryIndex + 1
3868
+ : 0;
3869
+ const historyPrefix = total > 0 ? "Response history " + selected + "/" + total + " · " : "";
3870
+ const thinkingLabel = hasThinking ? "assistant thinking" : "assistant thinking unavailable";
3871
+ referenceBadgeEl.textContent = time
3872
+ ? historyPrefix + thinkingLabel + " · " + time
3873
+ : historyPrefix + thinkingLabel;
3874
+ return;
3875
+ }
3876
+
3817
3877
  if (!hasResponse) {
3818
3878
  referenceBadgeEl.textContent = "Latest response: none";
3819
3879
  return;
@@ -3864,8 +3924,13 @@ ${cssVarsBlock}
3864
3924
  function updateSyncBadge(normalizedEditorText) {
3865
3925
  if (!syncBadgeEl) return;
3866
3926
 
3867
- if (!latestResponseHasContent) {
3868
- syncBadgeEl.textContent = "No response loaded";
3927
+ const showingThinking = rightView === "thinking";
3928
+ const hasComparableContent = showingThinking
3929
+ ? Boolean(latestResponseThinking && latestResponseThinking.trim())
3930
+ : latestResponseHasContent;
3931
+
3932
+ if (!hasComparableContent) {
3933
+ syncBadgeEl.textContent = showingThinking ? "No thinking loaded" : "No response loaded";
3869
3934
  syncBadgeEl.classList.remove("sync", "edited");
3870
3935
  return;
3871
3936
  }
@@ -3873,13 +3938,14 @@ ${cssVarsBlock}
3873
3938
  const normalizedEditor = typeof normalizedEditorText === "string"
3874
3939
  ? normalizedEditorText
3875
3940
  : normalizeForCompare(sourceTextEl.value);
3876
- const inSync = normalizedEditor === latestResponseNormalized;
3941
+ const targetNormalized = showingThinking ? latestResponseThinkingNormalized : latestResponseNormalized;
3942
+ const inSync = normalizedEditor === targetNormalized;
3877
3943
  if (inSync) {
3878
- syncBadgeEl.textContent = "In sync with response";
3944
+ syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
3879
3945
  syncBadgeEl.classList.add("sync");
3880
3946
  syncBadgeEl.classList.remove("edited");
3881
3947
  } else {
3882
- syncBadgeEl.textContent = "Out of sync with response";
3948
+ syncBadgeEl.textContent = showingThinking ? "Out of sync with thinking" : "Out of sync with response";
3883
3949
  syncBadgeEl.classList.add("edited");
3884
3950
  syncBadgeEl.classList.remove("sync");
3885
3951
  }
@@ -4411,6 +4477,15 @@ ${cssVarsBlock}
4411
4477
  return;
4412
4478
  }
4413
4479
 
4480
+ if (rightView === "thinking") {
4481
+ const thinking = latestResponseThinking;
4482
+ finishPreviewRender(critiqueViewEl);
4483
+ critiqueViewEl.innerHTML = thinking && thinking.trim()
4484
+ ? buildPlainMarkdownHtml(thinking)
4485
+ : "<pre class='plain-markdown'>No thinking available for this response.</pre>";
4486
+ return;
4487
+ }
4488
+
4414
4489
  const markdown = latestResponseMarkdown;
4415
4490
  if (!markdown || !markdown.trim()) {
4416
4491
  finishPreviewRender(critiqueViewEl);
@@ -4446,36 +4521,56 @@ ${cssVarsBlock}
4446
4521
 
4447
4522
  function updateResultActionButtons(normalizedEditorText) {
4448
4523
  const hasResponse = latestResponseHasContent;
4524
+ const hasThinking = Boolean(latestResponseThinking && latestResponseThinking.trim());
4449
4525
  const normalizedEditor = typeof normalizedEditorText === "string"
4450
4526
  ? normalizedEditorText
4451
4527
  : normalizeForCompare(sourceTextEl.value);
4452
4528
  const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
4529
+ const thinkingLoaded = hasThinking && normalizedEditor === latestResponseThinkingNormalized;
4453
4530
  const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
4531
+ const showingThinking = rightView === "thinking";
4454
4532
 
4455
4533
  const critiqueNotes = isCritiqueResponse ? latestCritiqueNotes : "";
4456
4534
  const critiqueNotesLoaded = Boolean(critiqueNotes) && normalizedEditor === latestCritiqueNotesNormalized;
4457
4535
 
4458
- loadResponseBtn.hidden = isCritiqueResponse;
4459
- loadCritiqueNotesBtn.hidden = !isCritiqueResponse;
4460
- loadCritiqueFullBtn.hidden = !isCritiqueResponse;
4536
+ if (showingThinking) {
4537
+ loadResponseBtn.hidden = false;
4538
+ loadCritiqueNotesBtn.hidden = true;
4539
+ loadCritiqueFullBtn.hidden = true;
4540
+
4541
+ loadResponseBtn.disabled = uiBusy || !hasThinking || thinkingLoaded;
4542
+ loadResponseBtn.textContent = !hasThinking
4543
+ ? "Thinking unavailable"
4544
+ : (thinkingLoaded ? "Thinking already in editor" : "Load thinking into editor");
4545
+
4546
+ copyResponseBtn.disabled = uiBusy || !hasThinking;
4547
+ copyResponseBtn.textContent = "Copy thinking text";
4548
+ } else {
4549
+ loadResponseBtn.hidden = isCritiqueResponse;
4550
+ loadCritiqueNotesBtn.hidden = !isCritiqueResponse;
4551
+ loadCritiqueFullBtn.hidden = !isCritiqueResponse;
4461
4552
 
4462
- loadResponseBtn.disabled = uiBusy || !hasResponse || responseLoaded || isCritiqueResponse;
4463
- loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
4553
+ loadResponseBtn.disabled = uiBusy || !hasResponse || responseLoaded || isCritiqueResponse;
4554
+ loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
4464
4555
 
4465
- loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
4466
- loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
4556
+ loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
4557
+ loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
4467
4558
 
4468
- loadCritiqueFullBtn.disabled = uiBusy || !isCritiqueResponse || responseLoaded;
4469
- loadCritiqueFullBtn.textContent = responseLoaded ? "Full critique already in editor" : "Load full critique into editor";
4559
+ loadCritiqueFullBtn.disabled = uiBusy || !isCritiqueResponse || responseLoaded;
4560
+ loadCritiqueFullBtn.textContent = responseLoaded ? "Full critique already in editor" : "Load full critique into editor";
4470
4561
 
4471
- copyResponseBtn.disabled = uiBusy || !hasResponse;
4562
+ copyResponseBtn.disabled = uiBusy || !hasResponse;
4563
+ copyResponseBtn.textContent = "Copy response text";
4564
+ }
4472
4565
 
4473
4566
  const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
4474
4567
  const exportText = rightView === "editor-preview" ? prepareEditorTextForPreview(sourceTextEl.value) : latestResponseMarkdown;
4475
4568
  const canExportPdf = rightPaneShowsPreview && Boolean(String(exportText || "").trim());
4476
4569
  if (exportPdfBtn) {
4477
4570
  exportPdfBtn.disabled = uiBusy || pdfExportInProgress || !canExportPdf;
4478
- if (rightView === "markdown") {
4571
+ if (rightView === "thinking") {
4572
+ exportPdfBtn.title = "Thinking view does not support PDF export yet.";
4573
+ } else if (rightView === "markdown") {
4479
4574
  exportPdfBtn.title = "Switch right pane to Response (Preview) or Editor (Preview) to export PDF.";
4480
4575
  } else if (!canExportPdf) {
4481
4576
  exportPdfBtn.title = "Nothing to export yet.";
@@ -4653,7 +4748,11 @@ ${cssVarsBlock}
4653
4748
  }
4654
4749
 
4655
4750
  function setRightView(nextView) {
4656
- rightView = nextView === "preview" ? "preview" : (nextView === "editor-preview" ? "editor-preview" : "markdown");
4751
+ rightView = nextView === "preview"
4752
+ ? "preview"
4753
+ : (nextView === "editor-preview"
4754
+ ? "editor-preview"
4755
+ : (nextView === "thinking" ? "thinking" : "markdown"));
4657
4756
  rightViewSelect.value = rightView;
4658
4757
 
4659
4758
  if (rightView !== "editor-preview" && responseEditorPreviewTimer) {
@@ -4980,7 +5079,12 @@ ${cssVarsBlock}
4980
5079
  }
4981
5080
 
4982
5081
  if (inFence) {
4983
- out.push(line.length > 0 ? highlightCodeLine(line, fenceLanguage) : "");
5082
+ out.push(line.length > 0 ? highlightCodeLine(line, fenceLanguage) : EMPTY_OVERLAY_LINE);
5083
+ continue;
5084
+ }
5085
+
5086
+ if (line.length === 0) {
5087
+ out.push(EMPTY_OVERLAY_LINE);
4984
5088
  continue;
4985
5089
  }
4986
5090
 
@@ -5019,7 +5123,7 @@ ${cssVarsBlock}
5019
5123
  const out = [];
5020
5124
  for (const line of lines) {
5021
5125
  if (line.length === 0) {
5022
- out.push("");
5126
+ out.push(EMPTY_OVERLAY_LINE);
5023
5127
  } else if (lang) {
5024
5128
  out.push(highlightCodeLine(line, lang));
5025
5129
  } else {
@@ -5330,18 +5434,20 @@ ${cssVarsBlock}
5330
5434
  return lower.indexOf("## critiques") !== -1 && lower.indexOf("## document") !== -1;
5331
5435
  }
5332
5436
 
5333
- function handleIncomingResponse(markdown, kind, timestamp) {
5437
+ function handleIncomingResponse(markdown, kind, timestamp, thinking) {
5334
5438
  const responseTimestamp =
5335
5439
  typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp > 0
5336
5440
  ? timestamp
5337
5441
  : Date.now();
5338
5442
 
5339
5443
  latestResponseMarkdown = markdown;
5444
+ latestResponseThinking = typeof thinking === "string" ? thinking : "";
5340
5445
  latestResponseKind = kind === "critique" ? "critique" : "annotation";
5341
5446
  latestResponseTimestamp = responseTimestamp;
5342
5447
  latestResponseIsStructuredCritique = isStructuredCritique(markdown);
5343
5448
  latestResponseHasContent = Boolean(markdown && markdown.trim());
5344
5449
  latestResponseNormalized = normalizeForCompare(markdown);
5450
+ latestResponseThinkingNormalized = normalizeForCompare(latestResponseThinking);
5345
5451
 
5346
5452
  if (latestResponseIsStructuredCritique) {
5347
5453
  latestCritiqueNotes = buildCritiqueNotesMarkdown(markdown);
@@ -5357,7 +5463,7 @@ ${cssVarsBlock}
5357
5463
  function applyLatestPayload(payload) {
5358
5464
  if (!payload || typeof payload.markdown !== "string") return false;
5359
5465
  const responseKind = payload.kind === "critique" ? "critique" : "annotation";
5360
- handleIncomingResponse(payload.markdown, responseKind, payload.timestamp);
5466
+ handleIncomingResponse(payload.markdown, responseKind, payload.timestamp, payload.thinking);
5361
5467
  return true;
5362
5468
  }
5363
5469
 
@@ -5456,7 +5562,7 @@ ${cssVarsBlock}
5456
5562
  message.lastResponse.kind === "critique"
5457
5563
  ? "critique"
5458
5564
  : (isStructuredCritique(lastMarkdown) ? "critique" : "annotation");
5459
- handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp);
5565
+ handleIncomingResponse(lastMarkdown, lastResponseKind, message.lastResponse.timestamp, message.lastResponse.thinking);
5460
5566
  }
5461
5567
 
5462
5568
  if (pendingRequestId) {
@@ -5553,7 +5659,7 @@ ${cssVarsBlock}
5553
5659
  }
5554
5660
 
5555
5661
  if (!appliedFromHistory && typeof message.markdown === "string") {
5556
- handleIncomingResponse(message.markdown, responseKind, message.timestamp);
5662
+ handleIncomingResponse(message.markdown, responseKind, message.timestamp, message.thinking);
5557
5663
  }
5558
5664
 
5559
5665
  if (responseKind === "critique") {
@@ -5582,6 +5688,7 @@ ${cssVarsBlock}
5582
5688
  const payload = {
5583
5689
  kind: message.kind === "critique" ? "critique" : "annotation",
5584
5690
  markdown: message.markdown,
5691
+ thinking: typeof message.thinking === "string" ? message.thinking : null,
5585
5692
  timestamp: message.timestamp,
5586
5693
  };
5587
5694
 
@@ -6172,6 +6279,17 @@ ${cssVarsBlock}
6172
6279
  });
6173
6280
 
6174
6281
  loadResponseBtn.addEventListener("click", () => {
6282
+ if (rightView === "thinking") {
6283
+ if (!latestResponseThinking.trim()) {
6284
+ setStatus("No thinking available for the selected response.", "warning");
6285
+ return;
6286
+ }
6287
+ setEditorText(latestResponseThinking, { preserveScroll: false, preserveSelection: false });
6288
+ setSourceState({ source: "blank", label: "assistant thinking", path: null });
6289
+ setStatus("Loaded thinking into editor.", "success");
6290
+ return;
6291
+ }
6292
+
6175
6293
  if (!latestResponseMarkdown.trim()) {
6176
6294
  setStatus("No response available yet.", "warning");
6177
6295
  return;
@@ -6210,14 +6328,15 @@ ${cssVarsBlock}
6210
6328
  });
6211
6329
 
6212
6330
  copyResponseBtn.addEventListener("click", async () => {
6213
- if (!latestResponseMarkdown.trim()) {
6214
- setStatus("No response available yet.", "warning");
6331
+ const content = rightView === "thinking" ? latestResponseThinking : latestResponseMarkdown;
6332
+ if (!content.trim()) {
6333
+ setStatus(rightView === "thinking" ? "No thinking available yet." : "No response available yet.", "warning");
6215
6334
  return;
6216
6335
  }
6217
6336
 
6218
6337
  try {
6219
- await navigator.clipboard.writeText(latestResponseMarkdown);
6220
- setStatus("Copied response text.", "success");
6338
+ await navigator.clipboard.writeText(content);
6339
+ setStatus(rightView === "thinking" ? "Copied thinking text." : "Copied response text.", "success");
6221
6340
  } catch (error) {
6222
6341
  setStatus("Clipboard write failed.", "warning");
6223
6342
  }
@@ -6635,6 +6754,7 @@ export default function (pi: ExtensionAPI) {
6635
6754
  }
6636
6755
  lastStudioResponse = {
6637
6756
  markdown: latest.markdown,
6757
+ thinking: latest.thinking,
6638
6758
  timestamp: latest.timestamp,
6639
6759
  kind: latest.kind,
6640
6760
  };
@@ -6897,6 +7017,7 @@ export default function (pi: ExtensionAPI) {
6897
7017
  type: "latest_response",
6898
7018
  kind: lastStudioResponse.kind,
6899
7019
  markdown: lastStudioResponse.markdown,
7020
+ thinking: lastStudioResponse.thinking,
6900
7021
  timestamp: lastStudioResponse.timestamp,
6901
7022
  responseHistory: studioResponseHistory,
6902
7023
  });
@@ -7711,11 +7832,14 @@ export default function (pi: ExtensionAPI) {
7711
7832
  const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
7712
7833
  const role = typeof message.role === "string" ? message.role : "";
7713
7834
  const markdown = extractAssistantText(event.message);
7835
+ const thinking = extractAssistantThinking(event.message);
7714
7836
  emitDebugEvent("message_end", {
7715
7837
  role,
7716
7838
  stopReason,
7717
7839
  hasMarkdown: Boolean(markdown),
7718
7840
  markdownLength: markdown ? markdown.length : 0,
7841
+ hasThinking: Boolean(thinking),
7842
+ thinkingLength: thinking ? thinking.length : 0,
7719
7843
  activeRequestId: activeRequest?.id ?? null,
7720
7844
  activeRequestKind: activeRequest?.kind ?? null,
7721
7845
  });
@@ -7742,6 +7866,7 @@ export default function (pi: ExtensionAPI) {
7742
7866
  const fallbackHistoryItem: StudioResponseHistoryItem = {
7743
7867
  id: randomUUID(),
7744
7868
  markdown,
7869
+ thinking,
7745
7870
  timestamp: Date.now(),
7746
7871
  kind: inferStudioResponseKind(markdown),
7747
7872
  prompt: fallbackPrompt,
@@ -7752,12 +7877,14 @@ export default function (pi: ExtensionAPI) {
7752
7877
 
7753
7878
  const latestItem = studioResponseHistory[studioResponseHistory.length - 1];
7754
7879
  const responseTimestamp = latestItem?.timestamp ?? Date.now();
7880
+ const responseThinking = latestItem?.thinking ?? thinking ?? null;
7755
7881
 
7756
7882
  if (activeRequest) {
7757
7883
  const requestId = activeRequest.id;
7758
7884
  const kind = activeRequest.kind;
7759
7885
  lastStudioResponse = {
7760
7886
  markdown,
7887
+ thinking: responseThinking,
7761
7888
  timestamp: responseTimestamp,
7762
7889
  kind,
7763
7890
  };
@@ -7765,6 +7892,7 @@ export default function (pi: ExtensionAPI) {
7765
7892
  requestId,
7766
7893
  kind,
7767
7894
  markdownLength: markdown.length,
7895
+ thinkingLength: responseThinking ? responseThinking.length : 0,
7768
7896
  stopReason,
7769
7897
  });
7770
7898
  broadcast({
@@ -7772,6 +7900,7 @@ export default function (pi: ExtensionAPI) {
7772
7900
  requestId,
7773
7901
  kind,
7774
7902
  markdown,
7903
+ thinking: lastStudioResponse.thinking,
7775
7904
  timestamp: lastStudioResponse.timestamp,
7776
7905
  responseHistory: studioResponseHistory,
7777
7906
  });
@@ -7783,18 +7912,21 @@ export default function (pi: ExtensionAPI) {
7783
7912
  const inferredKind = inferStudioResponseKind(markdown);
7784
7913
  lastStudioResponse = {
7785
7914
  markdown,
7915
+ thinking: responseThinking,
7786
7916
  timestamp: responseTimestamp,
7787
7917
  kind: inferredKind,
7788
7918
  };
7789
7919
  emitDebugEvent("broadcast_latest_response", {
7790
7920
  kind: inferredKind,
7791
7921
  markdownLength: markdown.length,
7922
+ thinkingLength: responseThinking ? responseThinking.length : 0,
7792
7923
  stopReason,
7793
7924
  });
7794
7925
  broadcast({
7795
7926
  type: "latest_response",
7796
7927
  kind: inferredKind,
7797
7928
  markdown,
7929
+ thinking: lastStudioResponse.thinking,
7798
7930
  timestamp: lastStudioResponse.timestamp,
7799
7931
  responseHistory: studioResponseHistory,
7800
7932
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",