pi-studio-opencode 0.1.0 → 0.2.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.
@@ -92,7 +92,11 @@ Object.keys(LANG_EXT_MAP).forEach((lang) => {
92
92
  });
93
93
  });
94
94
  const HIGHLIGHTED_LANGUAGES = ["markdown", "javascript", "typescript", "python", "bash", "json", "rust", "c", "cpp", "julia", "fortran", "r", "matlab", "latex", "diff"];
95
- const SUPPORTED_LANGUAGES = Object.keys(LANG_EXT_MAP);
95
+ const SUPPORTED_LANGUAGES = Object.keys(LANG_EXT_MAP).sort((a, b) => {
96
+ const labelA = String(LANG_EXT_MAP[a]?.label || a);
97
+ const labelB = String(LANG_EXT_MAP[b]?.label || b);
98
+ return labelA.localeCompare(labelB);
99
+ });
96
100
 
97
101
  const elements = {
98
102
  leftPane: document.getElementById("leftPane"),
@@ -116,6 +120,8 @@ const elements = {
116
120
  annotationModeSelect: document.getElementById("annotationModeSelect"),
117
121
  stripAnnotationsBtn: document.getElementById("stripAnnotationsBtn"),
118
122
  saveAnnotatedBtn: document.getElementById("saveAnnotatedBtn"),
123
+ lensSelect: document.getElementById("lensSelect"),
124
+ critiqueBtn: document.getElementById("critiqueBtn"),
119
125
  highlightSelect: document.getElementById("highlightSelect"),
120
126
  langSelect: document.getElementById("langSelect"),
121
127
  sourceHighlight: document.getElementById("sourceHighlight"),
@@ -134,6 +140,8 @@ const elements = {
134
140
  historyLastBtn: document.getElementById("historyLastBtn"),
135
141
  historyIndexBadge: document.getElementById("historyIndexBadge"),
136
142
  loadResponseBtn: document.getElementById("loadResponseBtn"),
143
+ loadCritiqueNotesBtn: document.getElementById("loadCritiqueNotesBtn"),
144
+ loadCritiqueFullBtn: document.getElementById("loadCritiqueFullBtn"),
137
145
  loadHistoryPromptBtn: document.getElementById("loadHistoryPromptBtn"),
138
146
  copyResponseBtn: document.getElementById("copyResponseBtn"),
139
147
  exportPdfBtn: document.getElementById("exportPdfBtn"),
@@ -505,7 +513,7 @@ function maybeArmCompletionTitleAttention(snapshot, { initial = false } = {}) {
505
513
 
506
514
  state.lastCompletedTurnKey = nextKey;
507
515
  if (!shouldShowTitleAttention()) return;
508
- armTitleAttention("● Response ready");
516
+ armTitleAttention(getRequestKind(snapshot?.lastCompletedTurn) === "critique" ? "Critique ready" : "● Response ready");
509
517
  }
510
518
 
511
519
  function applySnapshot(snapshot, options = {}) {
@@ -513,9 +521,75 @@ function applySnapshot(snapshot, options = {}) {
513
521
  maybeArmCompletionTitleAttention(state.snapshot, options);
514
522
  }
515
523
 
524
+ function getRequestKind(value) {
525
+ if (value && value.requestKind === "critique") return "critique";
526
+ if (value && value.promptMode === "response") return "response";
527
+ return "run";
528
+ }
529
+
530
+ function isCritiqueResponseMarkdown(markdown) {
531
+ const lower = String(markdown || "").toLowerCase();
532
+ return lower.includes("## critiques") && lower.includes("## document");
533
+ }
534
+
535
+ function isCritiqueHistoryItem(item) {
536
+ return Boolean(item) && (getRequestKind(item) === "critique" || isCritiqueResponseMarkdown(item.responseText || ""));
537
+ }
538
+
539
+ function isCritiqueDisplay(display) {
540
+ if (!display) return false;
541
+ if (display.kind === "history") {
542
+ return isCritiqueHistoryItem(display.item);
543
+ }
544
+ if (display.kind === "active") {
545
+ return getRequestKind(display.turn) === "critique";
546
+ }
547
+ return false;
548
+ }
549
+
550
+ function extractMarkdownSection(markdown, title) {
551
+ const heading = `## ${String(title || "").trim().toLowerCase()}`;
552
+ const lines = String(markdown || "").split("\n");
553
+ let start = -1;
554
+
555
+ for (let i = 0; i < lines.length; i++) {
556
+ if (String(lines[i] || "").trim().toLowerCase() === heading) {
557
+ start = i + 1;
558
+ break;
559
+ }
560
+ }
561
+
562
+ if (start < 0) return "";
563
+
564
+ const collected = [];
565
+ for (let i = start; i < lines.length; i++) {
566
+ const line = lines[i];
567
+ if (String(line || "").trim().startsWith("## ")) break;
568
+ collected.push(line);
569
+ }
570
+
571
+ return collected.join("\n").trim();
572
+ }
573
+
574
+ function buildCritiqueNotesMarkdown(markdown) {
575
+ const assessment = extractMarkdownSection(markdown, "Assessment");
576
+ const critiques = extractMarkdownSection(markdown, "Critiques");
577
+ const parts = [];
578
+
579
+ if (assessment) {
580
+ parts.push(`## Assessment\n\n${assessment}`);
581
+ }
582
+ if (critiques) {
583
+ parts.push(`## Critiques\n\n${critiques}`);
584
+ }
585
+
586
+ return parts.join("\n\n").trim();
587
+ }
588
+
516
589
  function getHistoryPromptButtonLabel(item) {
517
590
  if (!item) return "Load response prompt into editor";
518
591
  if (item.promptMode === "steer") return "Load effective prompt into editor";
592
+ if (item.promptMode === "run" && getRequestKind(item) === "critique") return "Load critique prompt into editor";
519
593
  if (item.promptMode === "run") return "Load run prompt into editor";
520
594
  return "Load response prompt into editor";
521
595
  }
@@ -523,6 +597,7 @@ function getHistoryPromptButtonLabel(item) {
523
597
  function getHistoryPromptLoadedStatus(item) {
524
598
  if (!item) return "Prompt unavailable for the selected response.";
525
599
  if (item.promptMode === "steer") return "Loaded effective prompt into editor.";
600
+ if (item.promptMode === "run" && getRequestKind(item) === "critique") return "Loaded critique prompt into editor.";
526
601
  if (item.promptMode === "run") return "Loaded run prompt into editor.";
527
602
  return "Loaded response prompt into editor.";
528
603
  }
@@ -530,12 +605,14 @@ function getHistoryPromptLoadedStatus(item) {
530
605
  function getHistoryPromptSourceStateLabel(item) {
531
606
  if (!item) return "response prompt";
532
607
  if (item.promptMode === "steer") return "effective prompt";
608
+ if (item.promptMode === "run" && getRequestKind(item) === "critique") return "critique prompt";
533
609
  if (item.promptMode === "run") return "run prompt";
534
610
  return "response prompt";
535
611
  }
536
612
 
537
613
  function getHistoryItemTitle(item) {
538
614
  if (!item) return "Response";
615
+ if (item.promptMode === "run" && getRequestKind(item) === "critique") return "Critique";
539
616
  if (item.promptMode === "run") return "Run";
540
617
  if (item.promptMode === "steer") return `Steer ${item.promptSteeringCount}`;
541
618
  return "Response";
@@ -1751,13 +1828,16 @@ async function renderResponsePreviewNow() {
1751
1828
 
1752
1829
  function formatPromptDescriptor(turn) {
1753
1830
  if (!turn) return "-";
1754
- return turn.promptMode === "run"
1755
- ? `chain ${turn.chainIndex} · run`
1756
- : `chain ${turn.chainIndex} · steer ${turn.promptSteeringCount}`;
1831
+ if (turn.promptMode === "run") {
1832
+ return getRequestKind(turn) === "critique"
1833
+ ? `chain ${turn.chainIndex} · critique`
1834
+ : `chain ${turn.chainIndex} · run`;
1835
+ }
1836
+ return `chain ${turn.chainIndex} · steer ${turn.promptSteeringCount}`;
1757
1837
  }
1758
1838
 
1759
1839
  function buildResponseDisplayText(item) {
1760
- if (!item) return "No response yet. Run editor text to generate a response.";
1840
+ if (!item) return "No response yet. Run editor text or critique editor text.";
1761
1841
  const responseText = String(item.responseText || "");
1762
1842
  if (item.responseError) {
1763
1843
  return responseText.trim()
@@ -1767,6 +1847,12 @@ function buildResponseDisplayText(item) {
1767
1847
  return responseText.trim() ? responseText : "(empty response)";
1768
1848
  }
1769
1849
 
1850
+ function buildThinkingDisplayText(item) {
1851
+ if (!item) return "No thinking available for this response.";
1852
+ const thinkingText = String(item.responseThinking || "");
1853
+ return thinkingText.trim() ? thinkingText : "No thinking available for this response.";
1854
+ }
1855
+
1770
1856
  function getActiveTurnMarkdown(turn) {
1771
1857
  if (!turn) return "";
1772
1858
  return String(turn.outputPreview || turn.responseText || "");
@@ -1780,19 +1866,44 @@ function getResponseReferenceLabel(display) {
1780
1866
  const selectedLabel = total > 0 && selectedIndex >= 0 ? `${selectedIndex + 1}/${total}` : `0/${total}`;
1781
1867
  const item = display.item;
1782
1868
  const time = formatReferenceTime(item.completedAt ?? item.submittedAt ?? 0);
1869
+ const responseLabel = isCritiqueHistoryItem(item) ? "assistant critique" : "assistant response";
1783
1870
  return time
1784
- ? `Response history ${selectedLabel} · assistant response · ${time}`
1785
- : `Response history ${selectedLabel} · assistant response`;
1871
+ ? `Response history ${selectedLabel} · ${responseLabel} · ${time}`
1872
+ : `Response history ${selectedLabel} · ${responseLabel}`;
1786
1873
  }
1787
1874
  if (display.kind === "active" && display.turn) {
1788
1875
  const time = formatReferenceTime(display.turn.firstOutputTextAt ?? display.turn.firstAssistantMessageAt ?? display.turn.submittedAt ?? 0);
1876
+ const responseLabel = getRequestKind(display.turn) === "critique" ? "Assistant critique in progress" : "Assistant response in progress";
1789
1877
  return time
1790
- ? `Assistant response in progress · ${time}`
1791
- : "Assistant response in progress";
1878
+ ? `${responseLabel} · ${time}`
1879
+ : responseLabel;
1792
1880
  }
1793
1881
  return "Latest response: none";
1794
1882
  }
1795
1883
 
1884
+ function getThinkingReferenceLabel(display) {
1885
+ if (!display) return "Thinking: none";
1886
+ if (display.kind === "history" && display.item) {
1887
+ const selectedIndex = getSelectedHistoryIndex();
1888
+ const total = getHistory().length;
1889
+ const selectedLabel = total > 0 && selectedIndex >= 0 ? `${selectedIndex + 1}/${total}` : `0/${total}`;
1890
+ const item = display.item;
1891
+ const hasThinking = Boolean(normalizedText(item.responseThinking || ""));
1892
+ const time = formatReferenceTime(item.completedAt ?? item.submittedAt ?? 0);
1893
+ const label = hasThinking ? "assistant thinking" : "assistant thinking unavailable";
1894
+ return time
1895
+ ? `Response history ${selectedLabel} · ${label} · ${time}`
1896
+ : `Response history ${selectedLabel} · ${label}`;
1897
+ }
1898
+ if (display.kind === "active" && display.turn) {
1899
+ const time = formatReferenceTime(display.turn.firstAssistantMessageAt ?? display.turn.submittedAt ?? 0);
1900
+ return time
1901
+ ? `Assistant thinking in progress · ${time}`
1902
+ : "Assistant thinking in progress";
1903
+ }
1904
+ return "Thinking: none";
1905
+ }
1906
+
1796
1907
  function getDisplayedResponse() {
1797
1908
  const activeTurn = state.followLatest ? state.snapshot?.activeTurn : null;
1798
1909
  const activeMarkdown = activeTurn ? getActiveTurnMarkdown(activeTurn) : "";
@@ -1825,7 +1936,9 @@ function getDisplayedResponse() {
1825
1936
  if (activeTurn) {
1826
1937
  return {
1827
1938
  kind: "active",
1828
- text: "Waiting for the active turn to produce a response.",
1939
+ text: getRequestKind(activeTurn) === "critique"
1940
+ ? "Waiting for the active critique to produce a response."
1941
+ : "Waiting for the active turn to produce a response.",
1829
1942
  markdown: "",
1830
1943
  hasContent: false,
1831
1944
  turn: activeTurn,
@@ -1835,7 +1948,41 @@ function getDisplayedResponse() {
1835
1948
 
1836
1949
  return {
1837
1950
  kind: "empty",
1838
- text: "No response yet. Run editor text to generate a response.",
1951
+ text: "No response yet. Run editor text or critique editor text.",
1952
+ markdown: "",
1953
+ hasContent: false,
1954
+ previewWarning: "",
1955
+ };
1956
+ }
1957
+
1958
+ function getDisplayedThinking() {
1959
+ const responseDisplay = getDisplayedResponse();
1960
+ if (responseDisplay.kind === "history" && responseDisplay.item) {
1961
+ const thinking = String(responseDisplay.item.responseThinking || "");
1962
+ return {
1963
+ kind: "history",
1964
+ text: buildThinkingDisplayText(responseDisplay.item),
1965
+ markdown: thinking,
1966
+ hasContent: Boolean(normalizedText(thinking)),
1967
+ item: responseDisplay.item,
1968
+ previewWarning: "",
1969
+ };
1970
+ }
1971
+
1972
+ if (responseDisplay.kind === "active" && responseDisplay.turn) {
1973
+ return {
1974
+ kind: "active",
1975
+ text: "Waiting for the active turn to finish before thinking becomes available.",
1976
+ markdown: "",
1977
+ hasContent: false,
1978
+ turn: responseDisplay.turn,
1979
+ previewWarning: "",
1980
+ };
1981
+ }
1982
+
1983
+ return {
1984
+ kind: "empty",
1985
+ text: "No thinking available for this response.",
1839
1986
  markdown: "",
1840
1987
  hasContent: false,
1841
1988
  previewWarning: "",
@@ -1876,6 +2023,8 @@ function formatTurnSummary(turn) {
1876
2023
  const effectiveEnd = turn.completedAt || now;
1877
2024
  return [
1878
2025
  ["Prompt", formatPromptDescriptor(turn)],
2026
+ ["Request", getRequestKind(turn)],
2027
+ ["Critique focus", turn.critiqueLens || "-"],
1879
2028
  ["Submitted", formatAbsoluteTime(turn.submittedAt)],
1880
2029
  ["To busy", formatRelativeDuration(turn.submittedAt, turn.backendBusyAt)],
1881
2030
  ["To first assistant", formatRelativeDuration(turn.submittedAt, turn.firstAssistantMessageAt)],
@@ -1940,6 +2089,8 @@ function renderSelectionPanel() {
1940
2089
  metaBlock.append(metaHeading);
1941
2090
  appendMetaRow(metaBlock, "chain", String(item.chainIndex));
1942
2091
  appendMetaRow(metaBlock, "mode", item.promptMode);
2092
+ appendMetaRow(metaBlock, "request", getRequestKind(item));
2093
+ appendMetaRow(metaBlock, "critique focus", item.critiqueLens || "-");
1943
2094
  appendMetaRow(metaBlock, "steering count", String(item.promptSteeringCount));
1944
2095
  appendMetaRow(metaBlock, "queued while busy", item.queuedWhileBusy ? "yes" : "no");
1945
2096
  appendMetaRow(metaBlock, "submitted", formatAbsoluteTime(item.submittedAt));
@@ -1954,6 +2105,7 @@ function renderSelectionPanel() {
1954
2105
  buildDetailBlock("Prompt text", item.promptText),
1955
2106
  buildDetailBlock("Effective prompt", item.effectivePrompt),
1956
2107
  buildDetailBlock("Response text", buildResponseDisplayText(item)),
2108
+ buildDetailBlock("Thinking text", buildThinkingDisplayText(item)),
1957
2109
  );
1958
2110
 
1959
2111
  elements.selectionPanel.innerHTML = "";
@@ -2016,6 +2168,7 @@ function renderHistoryDiagnostics() {
2016
2168
  badges.className = "badges";
2017
2169
  for (const [text, className] of [
2018
2170
  [item.promptMode, item.promptMode],
2171
+ [getRequestKind(item), getRequestKind(item) === "critique" ? "critique" : ""],
2019
2172
  [`steers:${item.promptSteeringCount}`, ""],
2020
2173
  [item.queuedWhileBusy ? "queued-busy" : "direct", ""],
2021
2174
  [item.responseError ? "error" : "ok", item.responseError ? "error" : ""],
@@ -2068,7 +2221,8 @@ function renderDiagnostics() {
2068
2221
  function renderResponsePane() {
2069
2222
  const history = getHistory();
2070
2223
  const selectedIndex = getSelectedHistoryIndex();
2071
- const display = getDisplayedResponse();
2224
+ const responseDisplay = getDisplayedResponse();
2225
+ const display = state.rightView === "thinking" ? getDisplayedThinking() : responseDisplay;
2072
2226
  updatePendingResponseScrollReset(display);
2073
2227
 
2074
2228
  if (elements.rightViewSelect) {
@@ -2078,6 +2232,19 @@ function renderResponsePane() {
2078
2232
  const selected = history.length && selectedIndex >= 0 ? selectedIndex + 1 : 0;
2079
2233
  elements.historyIndexBadge.textContent = `History: ${selected}/${history.length}`;
2080
2234
 
2235
+ if (state.rightView === "thinking") {
2236
+ cancelScheduledResponsePreviewRender();
2237
+ finishPreviewRender(elements.responseView);
2238
+ state.responsePreviewRenderNonce += 1;
2239
+ const thinkingText = String(display.text || "");
2240
+ state.currentRenderedPreviewKey = `thinking\u0000${display.kind}\u0000${thinkingText}`;
2241
+ setResponseViewHtml(buildPlainMarkdownHtml(thinkingText));
2242
+ applyPendingResponseScrollReset();
2243
+ scheduleResponsePaneRepaintNudge();
2244
+ elements.referenceBadge.textContent = getThinkingReferenceLabel(display);
2245
+ return;
2246
+ }
2247
+
2081
2248
  if (state.rightView === "markdown") {
2082
2249
  cancelScheduledResponsePreviewRender();
2083
2250
  finishPreviewRender(elements.responseView);
@@ -2113,7 +2280,7 @@ function renderEditorMeta() {
2113
2280
  const snapshot = state.snapshot;
2114
2281
  const history = getHistory();
2115
2282
  const selectedIndex = getSelectedHistoryIndex();
2116
- const display = getDisplayedResponse();
2283
+ const display = state.rightView === "thinking" ? getDisplayedThinking() : getDisplayedResponse();
2117
2284
  const editorTextNormalized = normalizedText(elements.promptInput.value);
2118
2285
  const displayedResponseNormalized = display?.hasContent ? normalizedText(display.markdown) : "";
2119
2286
  const inSync = Boolean(displayedResponseNormalized) && editorTextNormalized === displayedResponseNormalized;
@@ -2132,6 +2299,7 @@ function renderEditorMeta() {
2132
2299
  elements.historyCountBadge.textContent = `History: ${history.length && selectedIndex >= 0 ? selectedIndex + 1 : 0}/${history.length}`;
2133
2300
  elements.syncBadge.hidden = !inSync;
2134
2301
  elements.syncBadge.classList.toggle("sync", inSync);
2302
+ elements.syncBadge.textContent = state.rightView === "thinking" ? "In sync with thinking" : "In sync with response";
2135
2303
  if (elements.annotationModeSelect) {
2136
2304
  elements.annotationModeSelect.value = state.annotationsEnabled ? "on" : "off";
2137
2305
  elements.annotationModeSelect.title = state.annotationsEnabled
@@ -2199,7 +2367,13 @@ function deriveStatus() {
2199
2367
  return { message: "Studio: sending request to the attached session…", level: "", spinning: true };
2200
2368
  }
2201
2369
  if (snapshot.state.runState === "stopping") {
2202
- return { message: "Studio: stopping current run…", level: "warning", spinning: true };
2370
+ return {
2371
+ message: getRequestKind(snapshot.activeTurn) === "critique"
2372
+ ? "Studio: stopping current critique…"
2373
+ : "Studio: stopping current run…",
2374
+ level: "warning",
2375
+ spinning: true,
2376
+ };
2203
2377
  }
2204
2378
  if (snapshot.state.runState === "running") {
2205
2379
  const activeTurn = snapshot.activeTurn;
@@ -2211,9 +2385,15 @@ function deriveStatus() {
2211
2385
  let action = "Studio: waiting for queued steering";
2212
2386
  if (activeTurn) {
2213
2387
  if (activeTurn.promptMode === "run") {
2214
- action = activeTurn.firstOutputTextAt
2215
- ? "Studio: generating response"
2216
- : "Studio: running editor text";
2388
+ if (getRequestKind(activeTurn) === "critique") {
2389
+ action = activeTurn.firstOutputTextAt
2390
+ ? "Studio: generating critique"
2391
+ : "Studio: running critique";
2392
+ } else {
2393
+ action = activeTurn.firstOutputTextAt
2394
+ ? "Studio: generating response"
2395
+ : "Studio: running editor text";
2396
+ }
2217
2397
  } else {
2218
2398
  action = activeTurn.firstOutputTextAt
2219
2399
  ? `Studio: generating steering ${activeTurn.promptSteeringCount}`
@@ -2241,8 +2421,9 @@ function deriveStatus() {
2241
2421
  const selected = history.length && selectedIndex >= 0 ? selectedIndex + 1 : history.length;
2242
2422
  const time = formatReferenceTime(latest.completedAt ?? latest.submittedAt ?? 0);
2243
2423
  const suffix = time ? ` · ${time}` : "";
2424
+ const readyLabel = isCritiqueHistoryItem(latest) ? "critique" : "response";
2244
2425
  return {
2245
- message: `Ready · response ${selected}/${history.length}${suffix}`,
2426
+ message: `Ready · ${readyLabel} ${selected}/${history.length}${suffix}`,
2246
2427
  level: "",
2247
2428
  spinning: false,
2248
2429
  };
@@ -2343,18 +2524,42 @@ function updateActionState() {
2343
2524
  const snapshot = state.snapshot;
2344
2525
  const hasPrompt = Boolean(normalizedText(elements.promptInput.value));
2345
2526
  const runState = snapshot?.state?.runState ?? "idle";
2346
- const running = runState === "running" || runState === "stopping";
2527
+ const activeRequestKind = getRequestKind(snapshot?.activeTurn);
2528
+ const runIsStop = runState === "running" && activeRequestKind !== "critique";
2529
+ const critiqueIsStop = runState === "running" && activeRequestKind === "critique";
2530
+ const stoppingRun = runState === "stopping" && activeRequestKind !== "critique";
2531
+ const stoppingCritique = runState === "stopping" && activeRequestKind === "critique";
2347
2532
  const selectedItem = getSelectedHistoryItem();
2348
- const displayed = getDisplayedResponse();
2533
+ const responseDisplay = getDisplayedResponse();
2534
+ const displayed = state.rightView === "thinking" ? getDisplayedThinking() : responseDisplay;
2535
+ const displayedResponseText = displayed?.hasContent ? normalizedText(displayed.markdown) : "";
2349
2536
  const history = getHistory();
2350
2537
  const selectedIndex = getSelectedHistoryIndex();
2351
- const displayedResponseText = displayed?.hasContent ? normalizedText(displayed.markdown) : "";
2352
-
2353
- elements.runBtn.textContent = runState === "stopping" ? "Stopping…" : (running ? "Stop" : "Run editor text");
2354
- elements.runBtn.classList.toggle("request-stop-active", running);
2355
- elements.runBtn.disabled = !snapshot || state.busy || (!running && !hasPrompt) || runState === "stopping";
2356
-
2357
- elements.queueBtn.disabled = !snapshot || state.busy || runState !== "running" || !hasPrompt;
2538
+ const normalizedEditor = normalizedText(elements.promptInput.value);
2539
+ const selectedResponseItem = responseDisplay.kind === "history" ? responseDisplay.item : null;
2540
+ const critiqueHistoryItem = selectedResponseItem && isCritiqueHistoryItem(selectedResponseItem)
2541
+ ? selectedResponseItem
2542
+ : null;
2543
+ const structuredCritiqueItem = critiqueHistoryItem && isCritiqueResponseMarkdown(critiqueHistoryItem.responseText || "")
2544
+ ? critiqueHistoryItem
2545
+ : null;
2546
+ const critiqueNotes = structuredCritiqueItem ? buildCritiqueNotesMarkdown(structuredCritiqueItem.responseText || "") : "";
2547
+ const fullCritiqueText = structuredCritiqueItem ? String(structuredCritiqueItem.responseText || "") : "";
2548
+ const responseLoaded = Boolean(displayedResponseText) && normalizedEditor === displayedResponseText;
2549
+ const critiqueNotesLoaded = Boolean(critiqueNotes) && normalizedEditor === normalizedText(critiqueNotes);
2550
+ const fullCritiqueLoaded = Boolean(fullCritiqueText) && normalizedEditor === normalizedText(fullCritiqueText);
2551
+
2552
+ elements.runBtn.textContent = stoppingRun ? "Stopping…" : (runIsStop ? "Stop" : "Run editor text");
2553
+ elements.runBtn.classList.toggle("request-stop-active", runIsStop || stoppingRun);
2554
+ elements.runBtn.disabled = !snapshot || state.busy || runState === "stopping" || (runState === "running" ? !runIsStop : !hasPrompt);
2555
+
2556
+ if (elements.critiqueBtn) {
2557
+ elements.critiqueBtn.textContent = stoppingCritique ? "Stopping…" : (critiqueIsStop ? "Stop" : "Critique editor text");
2558
+ elements.critiqueBtn.classList.toggle("request-stop-active", critiqueIsStop || stoppingCritique);
2559
+ elements.critiqueBtn.disabled = !snapshot || state.busy || runState === "stopping" || (runState === "running" ? !critiqueIsStop : !hasPrompt);
2560
+ }
2561
+
2562
+ elements.queueBtn.disabled = !snapshot || state.busy || runState !== "running" || activeRequestKind === "critique" || !hasPrompt;
2358
2563
  elements.copyDraftBtn.disabled = !hasPrompt;
2359
2564
  if (elements.saveAsBtn) elements.saveAsBtn.disabled = state.busy || !hasPrompt;
2360
2565
  if (elements.saveBtn) elements.saveBtn.disabled = state.busy || !hasPrompt || !state.sourcePath;
@@ -2364,6 +2569,7 @@ function updateActionState() {
2364
2569
  if (elements.annotationModeSelect) elements.annotationModeSelect.disabled = state.busy;
2365
2570
  if (elements.stripAnnotationsBtn) elements.stripAnnotationsBtn.disabled = state.busy || !hasAnnotationMarkers(elements.promptInput.value);
2366
2571
  if (elements.saveAnnotatedBtn) elements.saveAnnotatedBtn.disabled = state.busy || !hasPrompt;
2572
+ if (elements.lensSelect) elements.lensSelect.disabled = state.busy || runState !== "idle";
2367
2573
  if (elements.highlightSelect) elements.highlightSelect.disabled = state.busy;
2368
2574
  if (elements.langSelect) elements.langSelect.disabled = state.busy;
2369
2575
  if (elements.responseHighlightSelect) {
@@ -2376,8 +2582,44 @@ function updateActionState() {
2376
2582
  elements.historyNextBtn.disabled = history.length === 0 || state.followLatest || selectedIndex < 0 || selectedIndex >= history.length - 1;
2377
2583
  elements.historyLastBtn.disabled = history.length === 0 || (state.followLatest && selectedIndex === history.length - 1);
2378
2584
 
2379
- elements.loadResponseBtn.disabled = state.busy || !displayedResponseText;
2380
- elements.loadResponseBtn.textContent = "Load response into editor";
2585
+ if (state.rightView === "thinking") {
2586
+ elements.loadResponseBtn.hidden = false;
2587
+ if (elements.loadCritiqueNotesBtn) elements.loadCritiqueNotesBtn.hidden = true;
2588
+ if (elements.loadCritiqueFullBtn) elements.loadCritiqueFullBtn.hidden = true;
2589
+
2590
+ elements.loadResponseBtn.disabled = state.busy || !displayedResponseText || responseLoaded;
2591
+ elements.loadResponseBtn.textContent = !displayedResponseText
2592
+ ? "Thinking unavailable"
2593
+ : (responseLoaded ? "Thinking already in editor" : "Load thinking into editor");
2594
+
2595
+ elements.copyResponseBtn.disabled = !displayedResponseText;
2596
+ elements.copyResponseBtn.textContent = "Copy thinking text";
2597
+ } else {
2598
+ const isStructuredCritique = Boolean(structuredCritiqueItem);
2599
+ elements.loadResponseBtn.hidden = isStructuredCritique;
2600
+ if (elements.loadCritiqueNotesBtn) elements.loadCritiqueNotesBtn.hidden = !isStructuredCritique;
2601
+ if (elements.loadCritiqueFullBtn) elements.loadCritiqueFullBtn.hidden = !isStructuredCritique;
2602
+
2603
+ elements.loadResponseBtn.disabled = state.busy || !displayedResponseText || responseLoaded || isStructuredCritique;
2604
+ elements.loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
2605
+
2606
+ if (elements.loadCritiqueNotesBtn) {
2607
+ elements.loadCritiqueNotesBtn.disabled = state.busy || !isStructuredCritique || !critiqueNotes || critiqueNotesLoaded;
2608
+ elements.loadCritiqueNotesBtn.textContent = critiqueNotesLoaded
2609
+ ? "Critique notes already in editor"
2610
+ : "Load critique notes into editor";
2611
+ }
2612
+ if (elements.loadCritiqueFullBtn) {
2613
+ elements.loadCritiqueFullBtn.disabled = state.busy || !isStructuredCritique || fullCritiqueLoaded;
2614
+ elements.loadCritiqueFullBtn.textContent = fullCritiqueLoaded
2615
+ ? "Full critique already in editor"
2616
+ : "Load full critique into editor";
2617
+ }
2618
+
2619
+ elements.copyResponseBtn.disabled = !displayedResponseText;
2620
+ elements.copyResponseBtn.textContent = critiqueHistoryItem ? "Copy critique text" : "Copy response text";
2621
+ }
2622
+
2381
2623
  if (!selectedItem) {
2382
2624
  elements.loadHistoryPromptBtn.disabled = true;
2383
2625
  elements.loadHistoryPromptBtn.textContent = getHistoryPromptButtonLabel(null);
@@ -2387,13 +2629,13 @@ function updateActionState() {
2387
2629
  elements.loadHistoryPromptBtn.textContent = getHistoryPromptButtonLabel(selectedItem);
2388
2630
  }
2389
2631
 
2390
- elements.copyResponseBtn.disabled = !displayedResponseText;
2391
-
2392
2632
  if (elements.exportPdfBtn) {
2393
2633
  const exportSource = getPdfExportSource();
2394
2634
  const canExportPdf = Boolean(exportSource && normalizedText(exportSource.markdown));
2395
2635
  elements.exportPdfBtn.disabled = state.pdfExportInProgress || !canExportPdf;
2396
- if (state.rightView === "markdown") {
2636
+ if (state.rightView === "thinking") {
2637
+ elements.exportPdfBtn.title = "Thinking view does not support PDF export yet.";
2638
+ } else if (state.rightView === "markdown") {
2397
2639
  elements.exportPdfBtn.title = "Switch right pane to Response (Preview) or Editor (Preview) to export PDF.";
2398
2640
  } else if (!canExportPdf) {
2399
2641
  elements.exportPdfBtn.title = "Nothing to export yet.";
@@ -2781,16 +3023,23 @@ async function copyText(text, successMessage) {
2781
3023
  setTransientStatus(successMessage, "success");
2782
3024
  }
2783
3025
 
3026
+ async function requestStopActiveRun(requestKind = getRequestKind(state.snapshot?.activeTurn)) {
3027
+ try {
3028
+ await postJson("/api/stop");
3029
+ setTransientStatus(requestKind === "critique" ? "Stop requested for critique." : "Stop requested.", "success");
3030
+ } catch (error) {
3031
+ setTransientStatus(error instanceof Error ? error.message : String(error), "error");
3032
+ }
3033
+ }
3034
+
2784
3035
  async function runOrStop() {
2785
3036
  const snapshot = state.snapshot;
2786
3037
  if (!snapshot) return;
2787
3038
  if (snapshot.state.runState === "running") {
2788
- try {
2789
- await postJson("/api/stop");
2790
- setTransientStatus("Stop requested.", "success");
2791
- } catch (error) {
2792
- setTransientStatus(error instanceof Error ? error.message : String(error), "error");
3039
+ if (getRequestKind(snapshot.activeTurn) === "critique") {
3040
+ return;
2793
3041
  }
3042
+ await requestStopActiveRun("run");
2794
3043
  return;
2795
3044
  }
2796
3045
 
@@ -2809,6 +3058,37 @@ async function runOrStop() {
2809
3058
  }
2810
3059
  }
2811
3060
 
3061
+ async function critiqueOrStop() {
3062
+ const snapshot = state.snapshot;
3063
+ if (!snapshot) return;
3064
+ if (snapshot.state.runState === "running") {
3065
+ if (getRequestKind(snapshot.activeTurn) !== "critique") {
3066
+ return;
3067
+ }
3068
+ await requestStopActiveRun("critique");
3069
+ return;
3070
+ }
3071
+
3072
+ const document = normalizedText(prepareEditorTextForSend(elements.promptInput.value));
3073
+ if (!document) {
3074
+ setTransientStatus("Add editor text before critique.", "warning");
3075
+ return;
3076
+ }
3077
+
3078
+ const lens = elements.lensSelect && ["auto", "writing", "code"].includes(elements.lensSelect.value)
3079
+ ? elements.lensSelect.value
3080
+ : "auto";
3081
+
3082
+ clearTitleAttention();
3083
+ try {
3084
+ const result = await postJson("/api/critique", { document, lens });
3085
+ const resolvedLens = ["writing", "code"].includes(result?.lens) ? result.lens : lens;
3086
+ setTransientStatus(`Running critique${resolvedLens === "auto" ? "" : ` (${resolvedLens})`}.`, "success");
3087
+ } catch (error) {
3088
+ setTransientStatus(error instanceof Error ? error.message : String(error), "error");
3089
+ }
3090
+ }
3091
+
2812
3092
  async function queueSteering() {
2813
3093
  const prompt = normalizedText(prepareEditorTextForSend(elements.promptInput.value));
2814
3094
  if (!prompt) {
@@ -2876,15 +3156,45 @@ function handleHistoryLast() {
2876
3156
  }
2877
3157
 
2878
3158
  function loadSelectedResponse() {
2879
- const display = getDisplayedResponse();
3159
+ const display = state.rightView === "thinking" ? getDisplayedThinking() : getDisplayedResponse();
2880
3160
  const responseText = display?.hasContent ? String(display.markdown || "") : "";
2881
3161
  if (!normalizedText(responseText)) {
2882
- setTransientStatus("No response available yet.", "warning");
3162
+ setTransientStatus(state.rightView === "thinking" ? "No thinking available for the selected response." : "No response available yet.", "warning");
2883
3163
  return;
2884
3164
  }
2885
- const originLabel = display.kind === "active" ? "live response" : "selected response";
3165
+ const originLabel = state.rightView === "thinking"
3166
+ ? "assistant thinking"
3167
+ : (display.kind === "active" ? "live response" : "selected response");
2886
3168
  setEditorText(responseText, originLabel, { sourcePath: null });
2887
- setTransientStatus("Loaded response into editor.", "success");
3169
+ setTransientStatus(state.rightView === "thinking" ? "Loaded thinking into editor." : "Loaded response into editor.", "success");
3170
+ }
3171
+
3172
+ function loadSelectedCritiqueNotes() {
3173
+ const display = getDisplayedResponse();
3174
+ const item = display.kind === "history" ? display.item : null;
3175
+ if (!item || !isCritiqueHistoryItem(item) || !isCritiqueResponseMarkdown(item.responseText || "")) {
3176
+ setTransientStatus("The selected response is not a structured critique.", "warning");
3177
+ return;
3178
+ }
3179
+ const notes = buildCritiqueNotesMarkdown(item.responseText || "");
3180
+ if (!notes) {
3181
+ setTransientStatus("No critique notes (Assessment/Critiques) found in the selected response.", "warning");
3182
+ return;
3183
+ }
3184
+ setEditorText(notes, "critique notes", { sourcePath: null });
3185
+ setTransientStatus("Loaded critique notes into editor.", "success");
3186
+ }
3187
+
3188
+ function loadSelectedCritiqueFull() {
3189
+ const display = getDisplayedResponse();
3190
+ const item = display.kind === "history" ? display.item : null;
3191
+ const fullCritique = String(item?.responseText || "");
3192
+ if (!item || !isCritiqueHistoryItem(item) || !isCritiqueResponseMarkdown(fullCritique)) {
3193
+ setTransientStatus("The selected response is not a structured critique.", "warning");
3194
+ return;
3195
+ }
3196
+ setEditorText(fullCritique, "full critique", { sourcePath: null });
3197
+ setTransientStatus("Loaded full critique into editor.", "success");
2888
3198
  }
2889
3199
 
2890
3200
  function loadSelectedPrompt() {
@@ -2903,14 +3213,21 @@ function loadSelectedPrompt() {
2903
3213
  }
2904
3214
 
2905
3215
  async function copySelectedResponse() {
2906
- const display = getDisplayedResponse();
3216
+ const display = state.rightView === "thinking" ? getDisplayedThinking() : getDisplayedResponse();
2907
3217
  const responseText = display?.hasContent ? String(display.markdown || "") : "";
2908
3218
  if (!normalizedText(responseText)) {
2909
- setTransientStatus("No response available yet.", "warning");
3219
+ setTransientStatus(state.rightView === "thinking" ? "No thinking available yet." : "No response available yet.", "warning");
2910
3220
  return;
2911
3221
  }
2912
3222
  try {
2913
- await copyText(responseText, display.kind === "active" ? "Copied live response preview." : "Copied response text.");
3223
+ await copyText(
3224
+ responseText,
3225
+ state.rightView === "thinking"
3226
+ ? "Copied thinking text."
3227
+ : (display.kind === "active"
3228
+ ? (getRequestKind(display.turn) === "critique" ? "Copied live critique preview." : "Copied live response preview.")
3229
+ : (isCritiqueDisplay(display) ? "Copied critique text." : "Copied response text.")),
3230
+ );
2914
3231
  } catch (error) {
2915
3232
  setTransientStatus(error instanceof Error ? error.message : String(error), "error");
2916
3233
  }
@@ -3033,9 +3350,13 @@ function handleGlobalShortcuts(event) {
3033
3350
  return;
3034
3351
  }
3035
3352
 
3036
- if (plainEscape && state.snapshot?.state?.runState === "running" && !elements.runBtn.disabled) {
3353
+ if (plainEscape && state.snapshot?.state?.runState === "running") {
3037
3354
  event.preventDefault();
3038
- void runOrStop();
3355
+ if (getRequestKind(state.snapshot?.activeTurn) === "critique") {
3356
+ void requestStopActiveRun("critique");
3357
+ } else {
3358
+ void requestStopActiveRun("run");
3359
+ }
3039
3360
  return;
3040
3361
  }
3041
3362
 
@@ -3085,6 +3406,9 @@ function wireEvents() {
3085
3406
 
3086
3407
  elements.diagnosticsBtn.addEventListener("click", () => toggleDiagnostics());
3087
3408
  elements.runBtn.addEventListener("click", () => void runOrStop());
3409
+ if (elements.critiqueBtn) {
3410
+ elements.critiqueBtn.addEventListener("click", () => void critiqueOrStop());
3411
+ }
3088
3412
  elements.queueBtn.addEventListener("click", () => void queueSteering());
3089
3413
  elements.copyDraftBtn.addEventListener("click", () => {
3090
3414
  void copyText(elements.promptInput.value, "Copied editor text.").catch((error) => {
@@ -3114,7 +3438,9 @@ function wireEvents() {
3114
3438
  elements.rightViewSelect.addEventListener("change", () => {
3115
3439
  state.rightView = elements.rightViewSelect.value === "editor-preview"
3116
3440
  ? "editor-preview"
3117
- : (elements.rightViewSelect.value === "markdown" ? "markdown" : "preview");
3441
+ : (elements.rightViewSelect.value === "markdown"
3442
+ ? "markdown"
3443
+ : (elements.rightViewSelect.value === "thinking" ? "thinking" : "preview"));
3118
3444
  state.currentRenderedPreviewKey = "";
3119
3445
  render();
3120
3446
  });
@@ -3149,6 +3475,12 @@ function wireEvents() {
3149
3475
  elements.historyNextBtn.addEventListener("click", () => handleHistoryNext());
3150
3476
  elements.historyLastBtn.addEventListener("click", () => handleHistoryLast());
3151
3477
  elements.loadResponseBtn.addEventListener("click", () => loadSelectedResponse());
3478
+ if (elements.loadCritiqueNotesBtn) {
3479
+ elements.loadCritiqueNotesBtn.addEventListener("click", () => loadSelectedCritiqueNotes());
3480
+ }
3481
+ if (elements.loadCritiqueFullBtn) {
3482
+ elements.loadCritiqueFullBtn.addEventListener("click", () => loadSelectedCritiqueFull());
3483
+ }
3152
3484
  elements.loadHistoryPromptBtn.addEventListener("click", () => loadSelectedPrompt());
3153
3485
  elements.copyResponseBtn.addEventListener("click", () => void copySelectedResponse());
3154
3486
  if (elements.exportPdfBtn) {