pi-studio 0.9.56 → 0.9.57

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
@@ -4,6 +4,22 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.57] — 2026-09-03
8
+
9
+ ### Added
10
+ - Add deterministic raw-pane submission displays and alignment anchors for Studio browser and `studio_repl_send` submissions across Shell, Python/IPython, Julia, R, GHCi, and Clojure.
11
+ - Add a browser-stored **Pane echo** selector with privacy-conscious **Off** default, adaptive **Summary**, and bounded **Full** modes, plus per-tool `echoMode` and `PI_STUDIO_REPL_ECHO_MODE` support.
12
+
13
+ ### Changed
14
+ - Keep ordinary panes quiet by default; opt-in Summary now shows short submissions in full, truncates longer ones after 6 lines or 600 source characters, and uses compact begin/completion anchors with a plain unanchored output divider instead of three metadata-heavy marker lines.
15
+ - Strip each request-specific header, source preview, divider, and footer from captured tool output and the Shared REPL Record while retaining the optional display in raw tmux history for human readability and future transcript alignment.
16
+ - Replace long per-request Studio loader paths with compact collision-resistant files under a private per-user `/tmp/pi-rc-<user-key>` root. Source files use mode `0600`; completed files are removed immediately, timeout/abort files remain only until the submission settles, and crash leftovers older than 24 hours are pruned on a later send.
17
+
18
+ ### Fixed
19
+ - Send GHCi completion signalling as a separate command so a submitted Haskell runtime error cannot prevent completion capture or leave the cross-client lease waiting until timeout.
20
+ - Persist a final clean-record error through the previously validated exact session identity when a Studio submission's tmux lifetime disappears before output capture, instead of leaving its sidecar entry stuck in `sending`.
21
+ - Strip the new compact loader path from browser-side optimistic capture as well as server capture, and stop later raw transcript refreshes from overwriting an authoritative finalized clean-record entry.
22
+
7
23
  ## [0.9.56] — 2026-09-02
8
24
 
9
25
  ### Added
package/README.md CHANGED
@@ -39,7 +39,7 @@ _The video shows an earlier version of the Studio interface. The basic workflow
39
39
  - Includes a right-pane **Changes** view for browsing the current git diff by file, previewing per-file diffs, opening changed files, loading the full diff into the editor, and copying the diff
40
40
  - Includes a right-pane **Files** view rooted in folders allowed for the current Studio session, with an **Allow folder…** action and location selector; exact-file grants can still be opened independently without exposing their parent folders. Files supports sorting by name/modified time/size, opening folders or the current root in Finder/the system file manager, loading text/code/CSV/TSV documents into the editor, opening a read-only **Preview (follow)** tab whose disk content is authoritative, previewing PDFs/images, opening previews in new Studio tabs, converting DOCX/ODT documents to editable Markdown when Pandoc is available after confirmation, copying paths, setting the current folder as the Studio working directory, and revealing files in the file manager
41
41
  - Imports detached text-file copies either through the browser's file picker or from a path on the computer running Pi, keeping the path option available in embedded and remote browser views; imported copies remain detached until saved or opened as file-backed documents
42
- - Includes an optional tmux-backed **REPL** view for Shell, Python, IPython, Julia, R, GHCi, and Clojure sessions, with Raw/Literate send modes, `Cmd/Ctrl+Shift+Enter` **Send to REPL**, session start/stop/interrupt controls, a compact refresh-persistent **Shared REPL Record** of compatible-client submissions, a secondary raw tmux mirror, agent-facing `studio_repl_status` / `studio_repl_send` tools, and Markdown/PDF/HTML export
42
+ - Includes an optional tmux-backed **REPL** view for Shell, Python, IPython, Julia, R, GHCi, and Clojure sessions, with Raw/Literate send modes, `Cmd/Ctrl+Shift+Enter` **Send to REPL**, session start/stop/interrupt controls, a privacy-conscious Off/Summary/Full pane-echo selector, a compact refresh-persistent **Shared REPL Record** of compatible-client submissions, a secondary raw tmux mirror, agent-facing `studio_repl_status` / `studio_repl_send` tools, and Markdown/PDF/HTML export
43
43
  - Includes a local persistent scratchpad for quick notes you want to keep out of the main editor until you're ready to copy or insert them, with a **Recent…** picker for recovering scratchpads saved under earlier file/draft identities
44
44
  - Includes a docked **Outline** rail for navigating document structure in the current editor text, with clickable entries that jump in the raw editor and reveal matching preview locations when available
45
45
  - Restores each browser tab’s editor workspace after refresh or cmux hidden-surface reconstruction, and provides an explicit **Reset editor** action when you want to discard the restored draft and return the tab to a fresh blank draft without changing responses or saved files
@@ -92,7 +92,7 @@ _The video shows an earlier version of the Studio interface. The basic workflow
92
92
  | `studio_export_pdf` | Export direct Markdown/LaTeX, a local file, or the last model response to PDF. Defaults to writing a file without opening a viewer. |
93
93
  | `studio_export_html` | Export direct Markdown/LaTeX, a local file, or the last model response to standalone HTML. Defaults to writing a file without opening a viewer. |
94
94
  | `studio_repl_status` | Inspect tmux-backed REPL sessions, including shared clean-record identity, compatible-client entries, and raw-mirror details. |
95
- | `studio_repl_send` | Send code to a visible REPL under the cross-client send lease and add its captured output to the shared clean record. |
95
+ | `studio_repl_send` | Send code to a visible REPL under the cross-client send lease, optionally choose its raw-pane `echoMode`, and add its captured output to the shared clean record. |
96
96
 
97
97
  ## Install
98
98
 
@@ -118,6 +118,20 @@ Compatible clients publish a versioned opaque record ID in tmux and keep the bou
118
118
 
119
119
  The clean record covers only submissions and notes whose boundaries a compatible client knows. Text typed directly into an attached tmux pane remains in the secondary raw pane/history mirror and is not presented as reliably parsed code/output. **Export record** produces a canonical Markdown representation with origin, mode, status, runtime, timestamp, and this limitation; PDF and HTML exports are derived from that Markdown.
120
120
 
121
+ ### Submission display and alignment anchors
122
+
123
+ Optional pane echo places submitted code after a compact begin anchor, followed by a plain `── output ──` divider and a completion anchor. The anchors contain a stable 12-character hash derived from the Shared REPL Record entry ID, so compatible clients can align known sends in future derived transcripts without exposing the entry ID itself. Studio strips the exact header, source preview, divider, and footer from captured tool output and the clean record; they remain visible only in the raw pane/history.
124
+
125
+ The REPL footer selector offers:
126
+
127
+ - **Off** (default): no optional submission display or anchors; the runtime's unavoidable temporary-file control command can still be echoed by the REPL.
128
+ - **Summary**: short submissions in full, truncating after 6 lines or 600 source characters, with compact begin/end anchors and a plain output divider.
129
+ - **Full**: up to 40 lines or 4,000 source characters with the same anchors and divider. Full mode writes source code into persistent raw terminal history, so use it only when that is acceptable.
130
+
131
+ The browser choice is stored locally for that Studio origin. `studio_repl_send` accepts the same per-call `echoMode`; `PI_STUDIO_REPL_ECHO_MODE=off|summary|full` changes the tool-send startup default from Off. Display text escapes terminal, line-separator, and bidirectional control characters. The anchors improve raw-history alignment but do not make direct pane activity without instrumentation authoritative or alter protocol-v1 entries.
132
+
133
+ Runtime wrappers use compact request-unique paths such as `/tmp/pi-rc-<user-key>/<token>.py` rather than a long Studio/session directory tree. The per-user root is current-user-owned mode `0700`, source files are mode `0600`, and files are removed after capture or by the timeout/abort watcher once execution settles. This keeps the unavoidable echoed loader command short without reverting to a collision-prone shared filename.
134
+
121
135
  See [`shared/REPL_SESSION_RECORD_PROTOCOL.md`](./shared/REPL_SESSION_RECORD_PROTOCOL.md) for protocol, safety, retention, and compatibility details.
122
136
 
123
137
  ## Side questions and research context
@@ -199,7 +213,7 @@ Studio only passes icon-pack arguments when a diagram actually references `lucid
199
213
  - Studio is designed as a complement to terminal pi, not a replacement.
200
214
  - Installing pi-studio makes the optional `pi-studio-dark` and `pi-studio-light` themes available in pi's theme selector; it does not change your active theme.
201
215
  - Editor/code font uses a best-effort terminal-monospace match when the current terminal config exposes it; set `PI_STUDIO_FONT_MONO` to force a specific CSS `font-family` stack. Use `PI_STUDIO_FONT_UI` or `PI_STUDIO_FONT_PROSE` to override the Studio UI or rendered-preview font stacks.
202
- - The optional REPL view requires `tmux`. Studio can start and stop Studio-owned `pi-studio-repl-*` sessions and can mirror detected `pi-repl-*` sessions, but it will not stop external `pi-repl-*` sessions. Compatible clients synchronize their clean record automatically; direct pane interaction remains available only in the raw mirror. A send timeout or tool abort does not stop submitted runtime code, so Studio keeps that session's cross-client lease until the completion marker appears or the exact tmux session ends.
216
+ - The optional REPL view requires `tmux`. Studio can start and stop Studio-owned `pi-studio-repl-*` sessions and can mirror detected `pi-repl-*` sessions, but it will not stop external `pi-repl-*` sessions. Compatible clients synchronize their clean record automatically; direct pane interaction remains available only in the raw mirror. A send timeout or tool abort does not stop submitted runtime code, so Studio keeps that session's cross-client lease until the runtime completion signal appears or the exact tmux session ends. The visible request-specific completion anchor is separate presentation metadata and stays in raw history when pane echo is enabled.
203
217
  - Side-question web search requires `BRAVE_API_KEY`; without it, local and conversation context still work, and the web option is shown as unavailable. Side sub-sessions are deliberately read-only and can run independently while the main Pi agent is busy.
204
218
  - Full preview/PDF quality depends on `pandoc` (and `xelatex` for PDF):
205
219
  - `brew install pandoc`
package/ROADMAP.md CHANGED
@@ -83,6 +83,20 @@ A coordinated interoperability release with `pi-repl` 0.4.0:
83
83
 
84
84
  This release does not change Studio's single-buffer architecture or move buffer-first work forward from `0.10.0`.
85
85
 
86
+ ## 0.9.57 — REPL submission display and alignment anchors (shipped 2026-09-03)
87
+
88
+ A coordinated presentation and future-alignment increment with `pi-repl` 0.4.1:
89
+
90
+ - keep optional pane echo Off by default, with an adaptive Summary that shows short submissions in full and a separately bounded Full choice;
91
+ - derive collision-resistant human-readable anchors from immutable clean-record entry IDs without exposing those IDs in the pane;
92
+ - retain compact begin/completion anchors and a plain unanchored output divider in raw tmux history while stripping the exact header, source preview, divider, and footer from captured output and protocol-v1 records;
93
+ - sanitize terminal controls, cap all visible source previews, and warn that Full mode persists bounded source code in raw history;
94
+ - cover Shell, Python/IPython, Julia, R, GHCi, and Clojure consistently across browser and tool sends, including error, timeout, abort, and exact-session-disappearance paths;
95
+ - replace verbose or fixed loader paths in both clients with compact request-unique files in a private per-user control root, retaining files through timed-out/aborted execution and cleaning them when the submission settles;
96
+ - treat markers only as presentation and alignment evidence, never as authority for silently promoting inferred direct activity into the clean record.
97
+
98
+ A separate Derived REPL Transcript can use these anchors later, but it remains outside protocol v1 and outside this release.
99
+
86
100
  ## 0.10.0 — Buffer-first editing
87
101
 
88
102
  The first architectural `0.10` release should add:
@@ -107,4 +121,5 @@ Cross-environment checks should cover, where relevant:
107
121
  - paths with spaces, Unicode, traversal attempts, and symlinks;
108
122
  - supported and unavailable browser APIs;
109
123
  - browser reconstruction, refresh recovery, and clean shutdown;
110
- - for shared REPL work: bidirectional visibility, compatible-client send contention, output attribution, restart persistence, clear/import behavior, stale or malformed records, and direct attached-pane activity.
124
+ - for shared REPL work: bidirectional visibility, compatible-client send contention, output attribution, restart persistence, clear/import behavior, stale or malformed records, and direct attached-pane activity;
125
+ - for REPL submission displays: Summary/Off/Full bounds, raw-versus-clean separation, stable cross-client anchors, compact private collision-resistant control files and cleanup, every supported runtime, runtime errors, timeout/abort lease retention, and exact-session disappearance.
@@ -132,6 +132,7 @@
132
132
  const queueSteerBtn = document.getElementById("queueSteerBtn");
133
133
  const sendReplBtn = document.getElementById("sendReplBtn");
134
134
  const replSendModeSelect = document.getElementById("replSendModeSelect");
135
+ const replEchoModeSelect = document.getElementById("replEchoModeSelect");
135
136
  const copyDraftBtn = document.getElementById("copyDraftBtn");
136
137
  const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
137
138
  const suggestCompletionOptionsBtn = document.getElementById("suggestCompletionOptionsBtn");
@@ -595,6 +596,14 @@
595
596
  return "raw";
596
597
  }
597
598
  })();
599
+ let replEchoMode = (() => {
600
+ try {
601
+ const stored = String((window.localStorage && window.localStorage.getItem("piStudio.replEchoMode.v2")) || "").trim().toLowerCase();
602
+ return stored === "summary" || stored === "full" ? stored : "off";
603
+ } catch {
604
+ return "off";
605
+ }
606
+ })();
598
607
  function normalizeReplJournalEntry(entry) {
599
608
  if (!entry || typeof entry !== "object") return null;
600
609
  const hasCompatibleOrigin = entry.origin === "pi-repl" || entry.origin === "pi-studio";
@@ -1353,6 +1362,11 @@
1353
1362
  return String(value || "").trim().toLowerCase() === "literate" ? "literate" : "raw";
1354
1363
  }
1355
1364
 
1365
+ function normalizeReplEchoMode(value) {
1366
+ const normalized = String(value || "").trim().toLowerCase();
1367
+ return normalized === "summary" || normalized === "full" ? normalized : "off";
1368
+ }
1369
+
1356
1370
  function isMacShortcutPlatform() {
1357
1371
  try {
1358
1372
  const platform = String((navigator && navigator.platform) || "");
@@ -1394,6 +1408,28 @@
1394
1408
  }
1395
1409
  }
1396
1410
 
1411
+ function setReplEchoMode(mode) {
1412
+ replEchoMode = normalizeReplEchoMode(mode);
1413
+ if (replEchoModeSelect) replEchoModeSelect.value = replEchoMode;
1414
+ try {
1415
+ if (window.localStorage) window.localStorage.setItem("piStudio.replEchoMode.v2", replEchoMode);
1416
+ } catch {
1417
+ // Ignore storage failures.
1418
+ }
1419
+ }
1420
+
1421
+ function syncReplEchoModeSelect(visible) {
1422
+ if (!replEchoModeSelect) return;
1423
+ replEchoModeSelect.hidden = !visible;
1424
+ replEchoModeSelect.disabled = !visible || wsState === "Disconnected" || uiBusy || replBusy;
1425
+ replEchoModeSelect.value = replEchoMode;
1426
+ replEchoModeSelect.title = replEchoMode === "off"
1427
+ ? "Do not add a submitted-code display or alignment anchors to the raw REPL pane."
1428
+ : (replEchoMode === "full"
1429
+ ? "Display up to 40 lines or 4,000 characters of submitted code; source remains in raw terminal history."
1430
+ : "Display short submissions in full, truncating after 6 lines or 600 characters, with compact anchors and a plain output divider.");
1431
+ }
1432
+
1397
1433
  function setReplJournalCollapsed(collapsed) {
1398
1434
  replJournalCollapsed = Boolean(collapsed);
1399
1435
  try {
@@ -1983,14 +2019,15 @@
1983
2019
  function stripStudioReplSubmissionEcho(delta) {
1984
2020
  let value = String(delta || "").replace(/^\s+/, "");
1985
2021
  // The raw mirror below remains raw; Studio record cards hide only the
1986
- // temp-file wrapper used to submit multiline snippets safely. The
1987
- // pi-studio-re fragment catches IPython's wrapped pi-studio-repl paths.
2022
+ // temp-file wrapper used to submit multiline snippets safely. Match
2023
+ // both legacy long Studio roots and compact private control roots.
1988
2024
  const submissionEchoPatterns = [
1989
- /^.*exec\(open\([\s\S]*?pi-studio-re[\s\S]*?globals\(\)\)\s*$/gm,
1990
- /^.*include\([\s\S]*?pi-studio-re[\s\S]*?\.jl"\)\s*$/gm,
1991
- /^.*source\([\s\S]*?pi-studio-re[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
1992
- /^.*:script\s+[\s\S]*?pi-studio-re[\s\S]*?\.ghci"?\s*$/gm,
1993
- /^.*\(do\s+\(load-file\s+[\s\S]*?pi-studio-re[\s\S]*?:pi-studio\/silent\)\s*$/gm,
2025
+ /^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?globals\(\)\)\s*$/gm,
2026
+ /^.*include\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.jl"\)\s*$/gm,
2027
+ /^.*source\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
2028
+ /^.*:script\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.ghci"?\s*$/gm,
2029
+ /^.*\(do\s+\(load-file\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?:pi-studio\/silent\)\s*$/gm,
2030
+ /^.*\.\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.sh[\s\S]*?(?:done\.flag|[a-f0-9]{16}\.done).*$/gm,
1994
2031
  ];
1995
2032
  for (const pattern of submissionEchoPatterns) {
1996
2033
  value = value.replace(pattern, "");
@@ -2026,6 +2063,10 @@
2026
2063
  if (entryIndex < 0) return false;
2027
2064
  const entry = replJournalEntries[entryIndex];
2028
2065
  if (entry.sessionName && sessionName && entry.sessionName !== sessionName) return false;
2066
+ if (!entry.legacyLocal && (entry.status === "captured" || entry.status === "timeout" || entry.status === "error")) {
2067
+ activeReplJournalEntryId = "";
2068
+ return false;
2069
+ }
2029
2070
  const delta = cleanReplCapturedOutput(extractReplTranscriptDelta(entry.beforeTranscript, transcript), entry);
2030
2071
  if (!delta.trim()) return false;
2031
2072
  if (entry.output === delta && entry.status === "captured") return false;
@@ -2231,6 +2272,7 @@
2231
2272
  requestId,
2232
2273
  sessionName: session.sessionName,
2233
2274
  text,
2275
+ echoMode: replEchoMode,
2234
2276
  journalEntryId: journalEntry.id,
2235
2277
  createdAt: journalEntry.createdAt,
2236
2278
  label: journalEntry.label,
@@ -22342,6 +22384,7 @@
22342
22384
  ? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
22343
22385
  : "Raw send: Send to REPL uses the selection, or full editor if no selection.";
22344
22386
  }
22387
+ syncReplEchoModeSelect(showReplSend);
22345
22388
  if (critiqueBtn) {
22346
22389
  critiqueBtn.textContent = "Critique text";
22347
22390
  critiqueBtn.classList.remove("request-stop-active");
@@ -22404,6 +22447,7 @@
22404
22447
  ? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
22405
22448
  : "Raw send: Send to REPL uses the selection, or full editor if no selection.";
22406
22449
  }
22450
+ syncReplEchoModeSelect(rightView === "repl");
22407
22451
 
22408
22452
  if (critiqueBtn) {
22409
22453
  critiqueBtn.textContent = critiqueIsStop ? "Stop" : "Critique text";
@@ -24960,6 +25004,19 @@
24960
25004
  });
24961
25005
  }
24962
25006
 
25007
+ if (replEchoModeSelect) {
25008
+ replEchoModeSelect.addEventListener("change", () => {
25009
+ setReplEchoMode(replEchoModeSelect.value);
25010
+ syncActionButtons();
25011
+ setStatus(replEchoMode === "full"
25012
+ ? "Full pane echo enabled. Bounded submitted source code will remain in raw terminal history."
25013
+ : (replEchoMode === "off"
25014
+ ? "Pane echo disabled; new sends will not add alignment anchors to raw terminal history."
25015
+ : "Summary pane echo enabled; short submissions will be shown with compact anchors and a plain output divider."),
25016
+ replEchoMode === "full" ? "warning" : "success");
25017
+ });
25018
+ }
25019
+
24963
25020
  copyDraftBtn.addEventListener("click", async () => {
24964
25021
  const content = sourceTextEl.value;
24965
25022
  if (!content.trim()) {
@@ -25580,6 +25637,7 @@
25580
25637
  const initialAnnotationsEnabled = storedAnnotationsEnabled ?? Boolean(annotationModeSelect ? annotationModeSelect.value !== "off" : true);
25581
25638
  setAnnotationsEnabled(initialAnnotationsEnabled, { silent: true });
25582
25639
  setReplSendMode(replSendMode);
25640
+ setReplEchoMode(replEchoMode);
25583
25641
 
25584
25642
  const sessionWorkspaceState = readPersistedWorkspaceState();
25585
25643
  const serverWorkspaceRecovery = await readServerWorkspaceRecoveryState();