pi-studio 0.9.55 → 0.9.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +14 -2
- package/ROADMAP.md +16 -1
- package/client/studio-client.js +163 -43
- package/index.ts +734 -163
- package/package.json +1 -1
- package/shared/REPL_SESSION_RECORD_PROTOCOL.md +70 -0
- package/shared/repl-session-record.js +623 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.56] — 2026-09-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Add a versioned, session-owned **Shared REPL Record** that synchronizes compatible-client code, notes, lifecycle state, and captured output bidirectionally with `pi-repl` while keeping both extensions independently usable.
|
|
11
|
+
- Discover records through first-writer-wins tmux metadata bound to the exact session ID and creation time, with private bounded snapshots, atomic locked updates, stale-lock recovery, and a cross-client send lease held through output capture; timed-out or aborted submissions keep that lease until the runtime completion marker or exact-session shutdown.
|
|
12
|
+
- Import legacy browser-local Studio REPL entries once by stable ID, propagate clear operations, expose shared record details through the browser and `studio_repl_status`, and document direct pane typing as raw-mirror-only activity.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Rename the compact Studio journal and export surfaces from **Studio REPL Record** to **Shared REPL Record**, identify entry origins, and make browser Markdown output byte-for-byte consistent with the canonical protocol renderer.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Prevent compatible Studio and `pi-repl` sends from concurrently attributing the same pane output to different entries.
|
|
19
|
+
|
|
7
20
|
## [0.9.55] — 2026-09-02
|
|
8
21
|
|
|
9
22
|
### Changed
|
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 **
|
|
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
|
|
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
|
|
@@ -91,6 +91,8 @@ _The video shows an earlier version of the Studio interface. The basic workflow
|
|
|
91
91
|
|---|---|
|
|
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
|
+
| `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. |
|
|
94
96
|
|
|
95
97
|
## Install
|
|
96
98
|
|
|
@@ -108,6 +110,16 @@ Run once without installing:
|
|
|
108
110
|
pi -e https://github.com/omaclaren/pi-studio
|
|
109
111
|
```
|
|
110
112
|
|
|
113
|
+
## Shared REPL record
|
|
114
|
+
|
|
115
|
+
Studio remains a standalone REPL client: it can create and use its own tmux sessions without `pi-repl`. When a compatible `pi-repl` installation uses the same tmux session, both clients automatically discover one session-owned clean record and see each other's code and captured output. No package imports or depends on the other.
|
|
116
|
+
|
|
117
|
+
Compatible clients publish a versioned opaque record ID in tmux and keep the bounded JSON record in a private per-user temporary directory. The record is tied to the exact tmux session ID and creation time, uses atomic locked updates, and serializes compatible sends from pre-capture through output capture so concurrent Studio and `pi-repl` submissions do not claim each other's output. Existing sessions attach lazily; Studio's legacy browser-local entries are imported once with stable IDs.
|
|
118
|
+
|
|
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
|
+
|
|
121
|
+
See [`shared/REPL_SESSION_RECORD_PROTOCOL.md`](./shared/REPL_SESSION_RECORD_PROTOCOL.md) for protocol, safety, retention, and compatibility details.
|
|
122
|
+
|
|
111
123
|
## Side questions and research context
|
|
112
124
|
|
|
113
125
|
Open **Review → Side question**, select **Side questions** in the right pane, or press **Cmd/Ctrl+Alt+Q**. A new side thread captures its starting text while keeping related-file access separate:
|
|
@@ -187,7 +199,7 @@ Studio only passes icon-pack arguments when a diagram actually references `lucid
|
|
|
187
199
|
- Studio is designed as a complement to terminal pi, not a replacement.
|
|
188
200
|
- 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.
|
|
189
201
|
- 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.
|
|
190
|
-
- 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.
|
|
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.
|
|
191
203
|
- 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.
|
|
192
204
|
- Full preview/PDF quality depends on `pandoc` (and `xelatex` for PDF):
|
|
193
205
|
- `brew install pandoc`
|
package/ROADMAP.md
CHANGED
|
@@ -69,6 +69,20 @@ A browser-neutral UX pass before buffer-first work:
|
|
|
69
69
|
- normalize Studio buttons and dropdown controls across Chromium and WebKit without changing native select interaction;
|
|
70
70
|
- expose X-high and Max side-question thinking only when supported by the active model, while retaining Low as the default.
|
|
71
71
|
|
|
72
|
+
## 0.9.56 — Shared REPL record (shipped 2026-09-02)
|
|
73
|
+
|
|
74
|
+
A coordinated interoperability release with `pi-repl` 0.4.0:
|
|
75
|
+
|
|
76
|
+
- make the clean structured record belong to the exact tmux session lifetime rather than one browser or extension;
|
|
77
|
+
- discover an opaque versioned record ID through first-writer-wins tmux metadata while keeping bounded content in private user-scoped sidecar storage;
|
|
78
|
+
- synchronize compatible submissions, literate notes, lifecycle status, captured output, clear operations, and bounded legacy Studio migration in both directions;
|
|
79
|
+
- hold one cross-client send lease from pre-send capture through completion capture—including after caller timeout or abort—so compatible clients cannot claim each other's output;
|
|
80
|
+
- preserve raw pane/history output as the honest source for direct attached-pane activity rather than inferring unreliable semantic boundaries;
|
|
81
|
+
- keep Studio and `pi-repl` independently installable and usable, with graceful fallback for unavailable, malformed, stale, or unsupported shared records;
|
|
82
|
+
- produce one canonical Markdown representation across Studio and `pi-repl`.
|
|
83
|
+
|
|
84
|
+
This release does not change Studio's single-buffer architecture or move buffer-first work forward from `0.10.0`.
|
|
85
|
+
|
|
72
86
|
## 0.10.0 — Buffer-first editing
|
|
73
87
|
|
|
74
88
|
The first architectural `0.10` release should add:
|
|
@@ -92,4 +106,5 @@ Cross-environment checks should cover, where relevant:
|
|
|
92
106
|
- local, SSH, and `--no-browser` Pi sessions;
|
|
93
107
|
- paths with spaces, Unicode, traversal attempts, and symlinks;
|
|
94
108
|
- supported and unavailable browser APIs;
|
|
95
|
-
- browser reconstruction, refresh recovery, and clean shutdown
|
|
109
|
+
- 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.
|
package/client/studio-client.js
CHANGED
|
@@ -597,13 +597,18 @@
|
|
|
597
597
|
})();
|
|
598
598
|
function normalizeReplJournalEntry(entry) {
|
|
599
599
|
if (!entry || typeof entry !== "object") return null;
|
|
600
|
+
const hasCompatibleOrigin = entry.origin === "pi-repl" || entry.origin === "pi-studio";
|
|
601
|
+
const sharedSynced = entry.sharedSynced === true || (entry.sharedSynced !== false && hasCompatibleOrigin);
|
|
600
602
|
const normalized = {
|
|
601
603
|
id: typeof entry.id === "string" && entry.id ? entry.id : ("repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8)),
|
|
602
604
|
requestId: typeof entry.requestId === "string" ? entry.requestId : "",
|
|
603
605
|
createdAt: typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt) ? entry.createdAt : Date.now(),
|
|
604
606
|
updatedAt: typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt) ? entry.updatedAt : Date.now(),
|
|
607
|
+
completedAt: typeof entry.completedAt === "number" && Number.isFinite(entry.completedAt) ? entry.completedAt : null,
|
|
605
608
|
sessionName: typeof entry.sessionName === "string" ? entry.sessionName : "",
|
|
606
609
|
runtime: typeof entry.runtime === "string" ? entry.runtime : "python",
|
|
610
|
+
origin: hasCompatibleOrigin ? entry.origin : "",
|
|
611
|
+
legacyLocal: !sharedSynced,
|
|
607
612
|
label: typeof entry.label === "string" ? entry.label : "REPL send",
|
|
608
613
|
mode: typeof entry.mode === "string" ? entry.mode : "raw",
|
|
609
614
|
prose: typeof entry.prose === "string" ? entry.prose : "",
|
|
@@ -635,8 +640,11 @@
|
|
|
635
640
|
requestId: entry.requestId,
|
|
636
641
|
createdAt: entry.createdAt,
|
|
637
642
|
updatedAt: entry.updatedAt,
|
|
643
|
+
completedAt: entry.completedAt,
|
|
638
644
|
sessionName: entry.sessionName,
|
|
639
645
|
runtime: entry.runtime,
|
|
646
|
+
origin: entry.origin,
|
|
647
|
+
sharedSynced: !entry.legacyLocal,
|
|
640
648
|
label: entry.label,
|
|
641
649
|
mode: entry.mode,
|
|
642
650
|
prose: entry.prose,
|
|
@@ -671,6 +679,7 @@
|
|
|
671
679
|
mode: existing.mode || entry.mode,
|
|
672
680
|
prose: existing.prose || entry.prose,
|
|
673
681
|
beforeTranscript: existing.beforeTranscript || "",
|
|
682
|
+
legacyLocal: entry.legacyLocal,
|
|
674
683
|
createdAt: existing.createdAt || entry.createdAt,
|
|
675
684
|
updatedAt: Math.max(existing.updatedAt || 0, entry.updatedAt || 0),
|
|
676
685
|
skippedChunks: existing.skippedChunks || entry.skippedChunks,
|
|
@@ -694,6 +703,7 @@
|
|
|
694
703
|
|
|
695
704
|
let replJournalEntries = loadPersistedReplJournalEntries();
|
|
696
705
|
let activeReplJournalEntryId = "";
|
|
706
|
+
const replJournalImportPendingSessions = new Set();
|
|
697
707
|
let replJournalCollapsed = (() => {
|
|
698
708
|
try {
|
|
699
709
|
const stored = window.localStorage ? window.localStorage.getItem("piStudio.replStudioCollapsed") : null;
|
|
@@ -1815,13 +1825,17 @@
|
|
|
1815
1825
|
}
|
|
1816
1826
|
|
|
1817
1827
|
function createReplJournalEntry(details) {
|
|
1828
|
+
const sharedSynced = details.sharedSynced === true;
|
|
1818
1829
|
return {
|
|
1819
1830
|
id: "repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8),
|
|
1820
1831
|
requestId: details.requestId || "",
|
|
1821
1832
|
createdAt: Date.now(),
|
|
1822
1833
|
updatedAt: Date.now(),
|
|
1834
|
+
completedAt: typeof details.completedAt === "number" ? details.completedAt : null,
|
|
1823
1835
|
sessionName: details.sessionName || "",
|
|
1824
1836
|
runtime: details.runtime || getActiveReplRuntime(),
|
|
1837
|
+
origin: details.origin === "pi-repl" ? "pi-repl" : "pi-studio",
|
|
1838
|
+
legacyLocal: !sharedSynced,
|
|
1825
1839
|
label: details.label || "REPL send",
|
|
1826
1840
|
mode: details.mode || replSendMode,
|
|
1827
1841
|
prose: String(details.prose || ""),
|
|
@@ -1840,6 +1854,65 @@
|
|
|
1840
1854
|
return entry;
|
|
1841
1855
|
}
|
|
1842
1856
|
|
|
1857
|
+
function syncReplJournalEntry(entry) {
|
|
1858
|
+
if (!entry || !entry.sessionName || wsState === "Disconnected") return false;
|
|
1859
|
+
return sendMessage({
|
|
1860
|
+
type: "repl_journal_upsert_request",
|
|
1861
|
+
requestId: makeRequestId(),
|
|
1862
|
+
sessionName: entry.sessionName,
|
|
1863
|
+
entry: {
|
|
1864
|
+
id: entry.id,
|
|
1865
|
+
requestId: entry.requestId,
|
|
1866
|
+
createdAt: entry.createdAt,
|
|
1867
|
+
updatedAt: entry.updatedAt,
|
|
1868
|
+
completedAt: entry.completedAt,
|
|
1869
|
+
runtime: entry.runtime,
|
|
1870
|
+
origin: "pi-studio",
|
|
1871
|
+
label: entry.label,
|
|
1872
|
+
mode: entry.mode,
|
|
1873
|
+
prose: entry.prose,
|
|
1874
|
+
code: entry.code,
|
|
1875
|
+
output: entry.output,
|
|
1876
|
+
status: entry.status,
|
|
1877
|
+
skippedChunks: entry.skippedChunks,
|
|
1878
|
+
},
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function maybeImportLegacyReplJournalEntries(sessionName) {
|
|
1883
|
+
const name = String(sessionName || "").trim();
|
|
1884
|
+
if (!name || replJournalImportPendingSessions.has(name) || wsState === "Disconnected") return false;
|
|
1885
|
+
const entries = replJournalEntries.filter((entry) => entry.sessionName === name && entry.legacyLocal).slice(-80);
|
|
1886
|
+
if (!entries.length) return false;
|
|
1887
|
+
replJournalImportPendingSessions.add(name);
|
|
1888
|
+
const sent = sendMessage({
|
|
1889
|
+
type: "repl_journal_import_request",
|
|
1890
|
+
requestId: makeRequestId(),
|
|
1891
|
+
sessionName: name,
|
|
1892
|
+
entries: entries.map((entry) => ({
|
|
1893
|
+
id: entry.id,
|
|
1894
|
+
requestId: entry.requestId,
|
|
1895
|
+
createdAt: entry.createdAt,
|
|
1896
|
+
updatedAt: entry.updatedAt,
|
|
1897
|
+
runtime: entry.runtime,
|
|
1898
|
+
origin: "pi-studio",
|
|
1899
|
+
label: entry.label,
|
|
1900
|
+
mode: entry.mode,
|
|
1901
|
+
prose: entry.prose,
|
|
1902
|
+
code: entry.code,
|
|
1903
|
+
output: entry.output,
|
|
1904
|
+
status: entry.status,
|
|
1905
|
+
skippedChunks: entry.skippedChunks,
|
|
1906
|
+
})),
|
|
1907
|
+
});
|
|
1908
|
+
if (!sent) {
|
|
1909
|
+
replJournalImportPendingSessions.delete(name);
|
|
1910
|
+
} else {
|
|
1911
|
+
window.setTimeout(() => replJournalImportPendingSessions.delete(name), 5000);
|
|
1912
|
+
}
|
|
1913
|
+
return sent;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1843
1916
|
function recordReplToolSend(message) {
|
|
1844
1917
|
const requestId = typeof message.toolCallId === "string" && message.toolCallId.trim()
|
|
1845
1918
|
? "tool:" + message.toolCallId.trim()
|
|
@@ -1853,6 +1926,8 @@
|
|
|
1853
1926
|
requestId,
|
|
1854
1927
|
sessionName,
|
|
1855
1928
|
runtime,
|
|
1929
|
+
origin: message.origin === "pi-repl" ? "pi-repl" : "pi-studio",
|
|
1930
|
+
sharedSynced: message.sharedSynced === true,
|
|
1856
1931
|
label: typeof message.label === "string" && message.label.trim() ? message.label.trim() : "Pi",
|
|
1857
1932
|
mode: "agent",
|
|
1858
1933
|
code,
|
|
@@ -1971,19 +2046,28 @@
|
|
|
1971
2046
|
function buildReplJournalMarkdown(entries) {
|
|
1972
2047
|
const visibleEntries = Array.isArray(entries) ? entries : getVisibleReplJournalEntries();
|
|
1973
2048
|
const sessionName = getActiveReplJournalSessionName();
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
2049
|
+
const updatedAt = visibleEntries.reduce((latest, entry) => Math.max(latest, Number(entry.updatedAt) || 0), 0) || Date.now();
|
|
2050
|
+
const lines = [
|
|
2051
|
+
"# Shared REPL Record",
|
|
2052
|
+
"",
|
|
2053
|
+
"Session: `" + (sessionName || "unknown") + "`",
|
|
2054
|
+
"Record protocol: pi-repl-session-record v1",
|
|
2055
|
+
"Updated: " + new Date(updatedAt).toISOString(),
|
|
2056
|
+
"",
|
|
2057
|
+
];
|
|
1977
2058
|
if (!visibleEntries.length) {
|
|
1978
|
-
lines.push(
|
|
2059
|
+
lines.push("_No compatible-client entries have been recorded for this tmux session._", "");
|
|
2060
|
+
lines.push("_This clean record contains submissions made through compatible clients. Commands typed directly into an attached tmux pane remain available only in the raw pane/history mirror._", "");
|
|
1979
2061
|
return lines.join("\n");
|
|
1980
2062
|
}
|
|
1981
2063
|
visibleEntries.forEach((entry, index) => {
|
|
1982
2064
|
lines.push("## " + (index + 1) + ". " + (entry.label || "REPL entry"));
|
|
1983
2065
|
lines.push("");
|
|
1984
|
-
lines.push("- Time: " + new Date(entry.createdAt || Date.now()).
|
|
2066
|
+
lines.push("- Time: " + new Date(entry.createdAt || Date.now()).toISOString());
|
|
2067
|
+
lines.push("- Origin: " + (entry.origin || "unknown"));
|
|
2068
|
+
lines.push("- Mode: " + (entry.mode || "raw"));
|
|
2069
|
+
lines.push("- Status: " + (entry.status || "sent"));
|
|
1985
2070
|
if (entry.runtime) lines.push("- Runtime: " + entry.runtime);
|
|
1986
|
-
if (entry.sessionName) lines.push("- Session: `" + entry.sessionName + "`");
|
|
1987
2071
|
if (entry.skippedChunks) lines.push("- Skipped chunks: " + entry.skippedChunks);
|
|
1988
2072
|
lines.push("");
|
|
1989
2073
|
if (String(entry.prose || "").trim()) {
|
|
@@ -1991,7 +2075,10 @@
|
|
|
1991
2075
|
lines.push("");
|
|
1992
2076
|
}
|
|
1993
2077
|
if (String(entry.code || "").trim()) {
|
|
1994
|
-
|
|
2078
|
+
const markdownRuntime = entry.runtime === "ipython"
|
|
2079
|
+
? "python"
|
|
2080
|
+
: (entry.runtime === "unknown" || entry.runtime === "shell" ? "" : entry.runtime);
|
|
2081
|
+
lines.push(getMarkdownFenceForText(entry.code, markdownRuntime));
|
|
1995
2082
|
lines.push("");
|
|
1996
2083
|
}
|
|
1997
2084
|
if (String(entry.output || "").trim()) {
|
|
@@ -2001,17 +2088,18 @@
|
|
|
2001
2088
|
lines.push("");
|
|
2002
2089
|
}
|
|
2003
2090
|
});
|
|
2091
|
+
lines.push("_This clean record contains submissions made through compatible clients. Commands typed directly into an attached tmux pane remain available only in the raw pane/history mirror._", "");
|
|
2004
2092
|
return lines.join("\n").replace(/\n{4,}/g, "\n\n\n").trimEnd() + "\n";
|
|
2005
2093
|
}
|
|
2006
2094
|
|
|
2007
2095
|
async function copyReplJournalToClipboard() {
|
|
2008
2096
|
const entries = getVisibleReplJournalEntries();
|
|
2009
2097
|
if (!entries.length) {
|
|
2010
|
-
setStatus("No
|
|
2098
|
+
setStatus("No shared REPL record entries to copy for this session yet.", "warning");
|
|
2011
2099
|
return;
|
|
2012
2100
|
}
|
|
2013
2101
|
if (await writeTextToClipboard(buildReplJournalMarkdown(entries))) {
|
|
2014
|
-
setStatus("Copied
|
|
2102
|
+
setStatus("Copied shared REPL record as Markdown.", "success");
|
|
2015
2103
|
} else {
|
|
2016
2104
|
setStatus("Clipboard write failed.", "warning");
|
|
2017
2105
|
}
|
|
@@ -2020,7 +2108,7 @@
|
|
|
2020
2108
|
function exportReplJournalMarkdown() {
|
|
2021
2109
|
const entries = getVisibleReplJournalEntries();
|
|
2022
2110
|
if (!entries.length) {
|
|
2023
|
-
setStatus("No
|
|
2111
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
2024
2112
|
return;
|
|
2025
2113
|
}
|
|
2026
2114
|
const blob = new Blob([buildReplJournalMarkdown(entries)], { type: "text/markdown;charset=utf-8" });
|
|
@@ -2029,12 +2117,12 @@
|
|
|
2029
2117
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2030
2118
|
const sessionSlug = getActiveReplJournalSessionName().replace(/[^-_.A-Za-z0-9]+/g, "-");
|
|
2031
2119
|
link.href = blobUrl;
|
|
2032
|
-
link.download = "repl-
|
|
2120
|
+
link.download = "repl-record" + (sessionSlug ? "-" + sessionSlug : "") + "-" + stamp + ".md";
|
|
2033
2121
|
document.body.appendChild(link);
|
|
2034
2122
|
link.click();
|
|
2035
2123
|
link.remove();
|
|
2036
2124
|
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
|
|
2037
|
-
setStatus("Exported
|
|
2125
|
+
setStatus("Exported shared REPL record Markdown.", "success");
|
|
2038
2126
|
}
|
|
2039
2127
|
|
|
2040
2128
|
function clearReplJournal() {
|
|
@@ -2046,30 +2134,33 @@
|
|
|
2046
2134
|
}
|
|
2047
2135
|
activeReplJournalEntryId = "";
|
|
2048
2136
|
persistReplJournalEntries();
|
|
2049
|
-
|
|
2137
|
+
if (sessionName) {
|
|
2138
|
+
sendMessage({ type: "repl_journal_clear_request", requestId: makeRequestId(), sessionName });
|
|
2139
|
+
}
|
|
2140
|
+
setStatus(sessionName ? "Cleared shared REPL record for this session." : "Cleared local REPL record cache.", "success");
|
|
2050
2141
|
renderReplViewIfActive({ force: true });
|
|
2051
2142
|
}
|
|
2052
2143
|
|
|
2053
2144
|
function loadReplJournalIntoEditor() {
|
|
2054
2145
|
const entries = getVisibleReplJournalEntries();
|
|
2055
2146
|
if (!entries.length) {
|
|
2056
|
-
setStatus("No
|
|
2147
|
+
setStatus("No shared REPL record entries to load for this session yet.", "warning");
|
|
2057
2148
|
return;
|
|
2058
2149
|
}
|
|
2059
2150
|
const markdown = buildReplJournalMarkdown(entries);
|
|
2060
2151
|
setEditorText(markdown, { preserveScroll: false, preserveSelection: false });
|
|
2061
|
-
setSourceState({ source: "blank", label: "
|
|
2152
|
+
setSourceState({ source: "blank", label: "Shared REPL Record", path: null });
|
|
2062
2153
|
setEditorLanguage("markdown");
|
|
2063
|
-
setStatus("Loaded
|
|
2154
|
+
setStatus("Loaded shared REPL record into editor.", "success");
|
|
2064
2155
|
}
|
|
2065
2156
|
|
|
2066
2157
|
function addSelectedReplJournalNote() {
|
|
2067
2158
|
const note = getSelectedOrCurrentParagraphForReplNote();
|
|
2068
2159
|
if (!note.trim()) {
|
|
2069
|
-
setStatus("Select prose or place the cursor in a paragraph to add a
|
|
2160
|
+
setStatus("Select prose or place the cursor in a paragraph to add a shared REPL record note.", "warning");
|
|
2070
2161
|
return;
|
|
2071
2162
|
}
|
|
2072
|
-
addReplJournalEntry({
|
|
2163
|
+
const entry = addReplJournalEntry({
|
|
2073
2164
|
label: "note",
|
|
2074
2165
|
prose: note,
|
|
2075
2166
|
status: "note",
|
|
@@ -2077,7 +2168,8 @@
|
|
|
2077
2168
|
sessionName: replActiveSessionName,
|
|
2078
2169
|
runtime: getActiveReplRuntime(),
|
|
2079
2170
|
});
|
|
2080
|
-
|
|
2171
|
+
syncReplJournalEntry(entry);
|
|
2172
|
+
setStatus("Added note to shared REPL record.", "success");
|
|
2081
2173
|
renderReplViewIfActive({ force: true });
|
|
2082
2174
|
}
|
|
2083
2175
|
|
|
@@ -2093,7 +2185,7 @@
|
|
|
2093
2185
|
}
|
|
2094
2186
|
if (payload.noteOnly) {
|
|
2095
2187
|
if (String(payload.prose || "").trim()) {
|
|
2096
|
-
addReplJournalEntry({
|
|
2188
|
+
const entry = addReplJournalEntry({
|
|
2097
2189
|
label: payload.label || "note",
|
|
2098
2190
|
prose: payload.prose,
|
|
2099
2191
|
status: "note",
|
|
@@ -2102,7 +2194,8 @@
|
|
|
2102
2194
|
runtime: getActiveReplRuntime(),
|
|
2103
2195
|
skippedChunks: payload.skippedChunks,
|
|
2104
2196
|
});
|
|
2105
|
-
|
|
2197
|
+
syncReplJournalEntry(entry);
|
|
2198
|
+
setStatus("Added prose to shared REPL record.", "success");
|
|
2106
2199
|
renderReplViewIfActive({ force: true });
|
|
2107
2200
|
} else {
|
|
2108
2201
|
setStatus("No code or prose found to send.", "warning");
|
|
@@ -2133,7 +2226,18 @@
|
|
|
2133
2226
|
renderReplViewIfActive({ force: true });
|
|
2134
2227
|
const skippedSuffix = payload.skippedChunks ? " (skipped " + payload.skippedChunks + " incompatible chunk" + (payload.skippedChunks === 1 ? "" : "s") + ")" : "";
|
|
2135
2228
|
setStatus("Sending " + (payload.label || "editor text") + " to REPL…" + skippedSuffix, "info");
|
|
2136
|
-
if (!sendMessage({
|
|
2229
|
+
if (!sendMessage({
|
|
2230
|
+
type: "repl_send_request",
|
|
2231
|
+
requestId,
|
|
2232
|
+
sessionName: session.sessionName,
|
|
2233
|
+
text,
|
|
2234
|
+
journalEntryId: journalEntry.id,
|
|
2235
|
+
createdAt: journalEntry.createdAt,
|
|
2236
|
+
label: journalEntry.label,
|
|
2237
|
+
mode: journalEntry.mode,
|
|
2238
|
+
prose: journalEntry.prose,
|
|
2239
|
+
skippedChunks: journalEntry.skippedChunks,
|
|
2240
|
+
})) {
|
|
2137
2241
|
replBusy = false;
|
|
2138
2242
|
replJournalEntries = replJournalEntries.map((entry) => entry.id === journalEntry.id ? { ...entry, status: "error" } : entry);
|
|
2139
2243
|
persistReplJournalEntries();
|
|
@@ -10223,7 +10327,7 @@
|
|
|
10223
10327
|
}
|
|
10224
10328
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
10225
10329
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
10226
|
-
setStatus("No
|
|
10330
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
10227
10331
|
return;
|
|
10228
10332
|
}
|
|
10229
10333
|
|
|
@@ -10468,7 +10572,7 @@
|
|
|
10468
10572
|
}
|
|
10469
10573
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
10470
10574
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
10471
|
-
setStatus("No
|
|
10575
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
10472
10576
|
return;
|
|
10473
10577
|
}
|
|
10474
10578
|
|
|
@@ -10501,7 +10605,7 @@
|
|
|
10501
10605
|
let filenameHint = exportingSideThread
|
|
10502
10606
|
? getSideQuestionTranscriptFilename(sideThreadExportedAt).replace(/\.md$/i, ".html")
|
|
10503
10607
|
: (exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.html")));
|
|
10504
|
-
let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "
|
|
10608
|
+
let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "Shared REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview"));
|
|
10505
10609
|
if (sourcePath) {
|
|
10506
10610
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
10507
10611
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
@@ -11479,6 +11583,7 @@
|
|
|
11479
11583
|
const parts = [];
|
|
11480
11584
|
const kind = getReplStudioEntryKind(entry);
|
|
11481
11585
|
if (kind !== "Raw") parts.push(kind);
|
|
11586
|
+
if (entry.origin) parts.push(entry.origin);
|
|
11482
11587
|
const time = formatReferenceTime(entry.createdAt);
|
|
11483
11588
|
if (time) parts.push(time);
|
|
11484
11589
|
if (entry.skippedChunks) parts.push("skipped " + String(entry.skippedChunks));
|
|
@@ -11532,12 +11637,12 @@
|
|
|
11532
11637
|
const toggleButton = "<button type='button' data-repl-action='journal-toggle' aria-expanded='" + (replJournalCollapsed ? "false" : "true") + "'>" + (replJournalCollapsed ? "Show record" : "Hide record") + "</button>";
|
|
11533
11638
|
const toggleActions = "<div class='repl-journal-actions'>" + toggleButton + "</div>";
|
|
11534
11639
|
const summaryText = hasEntries
|
|
11535
|
-
? (entryCount + "
|
|
11536
|
-
: (sessionName ? "No
|
|
11640
|
+
? (entryCount + " shared record entr" + (entryCount === 1 ? "y" : "ies") + (sessionName ? " for " + sessionName : "") + ". Export is Markdown.")
|
|
11641
|
+
: (sessionName ? "No compatible-client entries for " + sessionName + "." : "pi-repl and pi-studio submissions will appear here.");
|
|
11537
11642
|
if (replJournalCollapsed) {
|
|
11538
11643
|
return "<section class='repl-journal repl-journal-compact" + collapsedClass + "'>"
|
|
11539
11644
|
+ "<div class='repl-journal-compact-row'>"
|
|
11540
|
-
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>
|
|
11645
|
+
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>Shared REPL Record</span><span>" + escapeHtml(summaryText) + "</span></div>"
|
|
11541
11646
|
+ "<div class='repl-journal-actions'>" + toggleButton + "</div>"
|
|
11542
11647
|
+ "</div>"
|
|
11543
11648
|
+ "</section>";
|
|
@@ -11575,14 +11680,14 @@
|
|
|
11575
11680
|
}).join("");
|
|
11576
11681
|
const emptyText = sessionName
|
|
11577
11682
|
? (String(transcript || "").trim()
|
|
11578
|
-
? "No
|
|
11579
|
-
: "No
|
|
11580
|
-
: "No
|
|
11683
|
+
? "No compatible-client record entries yet. The raw tmux mirror below still has this session's history; send code through pi-repl or Studio to build a clean record."
|
|
11684
|
+
: "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.")
|
|
11685
|
+
: "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.";
|
|
11581
11686
|
const terminalContent = banner
|
|
11582
11687
|
+ (hasEntries ? cards : "<div class='repl-studio-empty'>" + escapeHtml(emptyText) + "</div>");
|
|
11583
11688
|
return "<section class='repl-journal'>"
|
|
11584
|
-
+ "<div class='repl-journal-header'><h3>
|
|
11585
|
-
+ "<p class='repl-journal-description'>Clean record for
|
|
11689
|
+
+ "<div class='repl-journal-header'><h3>Shared REPL Record</h3>" + toggleActions + "</div>"
|
|
11690
|
+
+ "<p class='repl-journal-description'>Clean record shared by compatible pi-repl and pi-studio clients for this exact tmux session. Direct pane typing remains in the raw mirror below.</p>"
|
|
11586
11691
|
+ (omitted ? "<div class='repl-journal-omitted'>Showing latest 12 entries for this session; " + escapeHtml(String(omitted)) + " older entries remain in export.</div>" : "")
|
|
11587
11692
|
+ "<div class='repl-journal-list'>" + terminalContent + "</div>"
|
|
11588
11693
|
+ "</section>";
|
|
@@ -11606,7 +11711,7 @@
|
|
|
11606
11711
|
+ "</section>";
|
|
11607
11712
|
}
|
|
11608
11713
|
return "<section class='repl-mirror'>"
|
|
11609
|
-
+ "<div class='repl-journal-header'><div><h3>Raw REPL Mirror</h3><p>Best-effort tmux pane mirror. Useful for directly typed commands and debugging; the
|
|
11714
|
+
+ "<div class='repl-journal-header'><div><h3>Raw REPL Mirror</h3><p>Best-effort tmux pane mirror. Useful for directly typed commands and debugging; the shared clean record above has reliable client-submission boundaries.</p></div>" + actions + "</div>"
|
|
11610
11715
|
+ body
|
|
11611
11716
|
+ "</section>";
|
|
11612
11717
|
}
|
|
@@ -11656,7 +11761,7 @@
|
|
|
11656
11761
|
+ "<button type='button' data-repl-action='interrupt'" + (activeSession && !replBusy ? "" : " disabled") + " title='Send Ctrl+C to the active REPL session.'>Interrupt</button>"
|
|
11657
11762
|
+ "<button type='button' data-repl-action='copy-attach-command'" + (activeSession ? "" : " disabled") + " title='Copy command for attaching to this tmux session in a terminal.'>Copy attach command</button>"
|
|
11658
11763
|
+ "<button type='button' data-repl-action='run-all-chunks'" + (canSendToActiveSession ? "" : " disabled") + " title='Literate send: send all fenced code chunks matching the active REPL runtime.'>Run all chunks</button>"
|
|
11659
|
-
+ "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to the
|
|
11764
|
+
+ "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to the shared REPL record (Literate send) without sending it to the runtime.'>Add note</button>"
|
|
11660
11765
|
+ "<button type='button' data-repl-action='refresh'>Refresh</button>"
|
|
11661
11766
|
+ "<button type='button' data-repl-action='follow'>Follow: " + (replFollow ? "On" : "Off") + "</button>"
|
|
11662
11767
|
+ "</div>"
|
|
@@ -13677,7 +13782,7 @@
|
|
|
13677
13782
|
} else if (exportingSideThread) {
|
|
13678
13783
|
exportPdfBtn.title = "Save or copy Markdown, open it in an editor, or export the visible side discussion as PDF or HTML.";
|
|
13679
13784
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
13680
|
-
exportPdfBtn.title = "No
|
|
13785
|
+
exportPdfBtn.title = "No shared REPL record entries to export for this session yet.";
|
|
13681
13786
|
} else if (rightView === "markdown") {
|
|
13682
13787
|
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export.";
|
|
13683
13788
|
} else if (!canExportPreview) {
|
|
@@ -13685,7 +13790,7 @@
|
|
|
13685
13790
|
} else if (isHtmlArtifactPreview) {
|
|
13686
13791
|
exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
|
|
13687
13792
|
} else if (exportingReplJournal) {
|
|
13688
|
-
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the
|
|
13793
|
+
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the shared REPL record.";
|
|
13689
13794
|
} else {
|
|
13690
13795
|
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the current right-pane preview.";
|
|
13691
13796
|
}
|
|
@@ -13697,7 +13802,7 @@
|
|
|
13697
13802
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13698
13803
|
: (isHtmlArtifactPreview
|
|
13699
13804
|
? "Interactive HTML preview PDF export is not available yet."
|
|
13700
|
-
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in Studio." : (exportingReplJournal ? "Export the
|
|
13805
|
+
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in Studio." : (exportingReplJournal ? "Export the shared REPL record as PDF and open it in Studio." : "Export the current right-pane preview as PDF and open it in Studio.")));
|
|
13701
13806
|
}
|
|
13702
13807
|
if (exportPreviewPdfBtn) {
|
|
13703
13808
|
exportPreviewPdfBtn.disabled = exportBusy || !canExportPreview || isHtmlArtifactPreview || sideThreadRenderTooLarge;
|
|
@@ -13705,7 +13810,7 @@
|
|
|
13705
13810
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13706
13811
|
: (isHtmlArtifactPreview
|
|
13707
13812
|
? "Interactive HTML preview PDF export is not available yet."
|
|
13708
|
-
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in the default PDF viewer." : (exportingReplJournal ? "Export the
|
|
13813
|
+
: (exportingSideThread ? "Export the side-thread transcript as PDF and open it in the default PDF viewer." : (exportingReplJournal ? "Export the shared REPL record as PDF and open it in the default PDF viewer." : "Export the current right-pane preview as PDF and open it in the default PDF viewer.")));
|
|
13709
13814
|
}
|
|
13710
13815
|
if (exportPreviewHtmlStudioBtn) {
|
|
13711
13816
|
exportPreviewHtmlStudioBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
@@ -13713,7 +13818,7 @@
|
|
|
13713
13818
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13714
13819
|
: (isHtmlArtifactPreview
|
|
13715
13820
|
? "Export the authored HTML preview and open it in a new Studio editor tab."
|
|
13716
|
-
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in a new Studio editor tab." : (exportingReplJournal ? "Export the
|
|
13821
|
+
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in a new Studio editor tab." : (exportingReplJournal ? "Export the shared REPL record as standalone HTML and open it in a new Studio editor tab." : "Export the current right-pane preview as standalone HTML and open it in a new Studio editor tab.")));
|
|
13717
13822
|
}
|
|
13718
13823
|
if (exportPreviewHtmlBtn) {
|
|
13719
13824
|
exportPreviewHtmlBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
@@ -13721,7 +13826,7 @@
|
|
|
13721
13826
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13722
13827
|
: (isHtmlArtifactPreview
|
|
13723
13828
|
? "Export the authored HTML preview and open it in the default browser."
|
|
13724
|
-
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in the default browser." : (exportingReplJournal ? "Export the
|
|
13829
|
+
: (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in the default browser." : (exportingReplJournal ? "Export the shared REPL record as standalone HTML and open it in the default browser." : "Export the current right-pane preview as standalone HTML and open it in the default browser.")));
|
|
13725
13830
|
}
|
|
13726
13831
|
if (exportPreviewControlsEl) {
|
|
13727
13832
|
exportPreviewControlsEl.hidden = rightView === "editor-quarto-preview";
|
|
@@ -13729,11 +13834,11 @@
|
|
|
13729
13834
|
? (exportingSideThread
|
|
13730
13835
|
? "Choose a durable export for the visible side discussion."
|
|
13731
13836
|
: (exportingReplJournal
|
|
13732
|
-
? "Choose a format and export destination for the
|
|
13837
|
+
? "Choose a format and export destination for the shared REPL record."
|
|
13733
13838
|
: (isHtmlArtifactPreview ? "Export this HTML preview to Studio or browser." : "Choose a format and export destination for the current right-pane preview.")))
|
|
13734
13839
|
: (exportingSideThread
|
|
13735
13840
|
? "No completed side discussion is available to export yet."
|
|
13736
|
-
: (exportingReplJournal ? "No
|
|
13841
|
+
: (exportingReplJournal ? "No shared REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting."));
|
|
13737
13842
|
}
|
|
13738
13843
|
if (!canExportPreview || previewExportInProgress || sideQuestionMarkdownExportRequest) {
|
|
13739
13844
|
closeExportPreviewMenu();
|
|
@@ -22911,6 +23016,7 @@
|
|
|
22911
23016
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
22912
23017
|
}
|
|
22913
23018
|
const journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23019
|
+
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
22914
23020
|
if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
|
|
22915
23021
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
22916
23022
|
replError = typeof message.replError === "string" ? message.replError : (typeof message.captureError === "string" ? message.captureError : "");
|
|
@@ -22964,6 +23070,7 @@
|
|
|
22964
23070
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
22965
23071
|
}
|
|
22966
23072
|
let journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23073
|
+
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
22967
23074
|
if (typeof message.transcript === "string") {
|
|
22968
23075
|
replTranscript = trimReplTranscript(message.transcript);
|
|
22969
23076
|
journalChanged = updateActiveReplJournalEntryFromTranscript(
|
|
@@ -22988,6 +23095,19 @@
|
|
|
22988
23095
|
return;
|
|
22989
23096
|
}
|
|
22990
23097
|
|
|
23098
|
+
if (message.type === "repl_journal_ack") {
|
|
23099
|
+
const sessionName = typeof message.sessionName === "string" ? message.sessionName : "";
|
|
23100
|
+
if (sessionName) replJournalImportPendingSessions.delete(sessionName);
|
|
23101
|
+
if (message.cleared && sessionName) {
|
|
23102
|
+
replJournalEntries = replJournalEntries.filter((entry) => entry.sessionName !== sessionName);
|
|
23103
|
+
persistReplJournalEntries();
|
|
23104
|
+
} else {
|
|
23105
|
+
mergeReplJournalEntries(message.journalEntries);
|
|
23106
|
+
}
|
|
23107
|
+
renderReplViewIfActive({ force: true });
|
|
23108
|
+
return;
|
|
23109
|
+
}
|
|
23110
|
+
|
|
22991
23111
|
if (message.type === "repl_send_ack") {
|
|
22992
23112
|
replBusy = false;
|
|
22993
23113
|
replMessage = "";
|