pi-studio 0.9.55 → 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 +29 -0
- package/README.md +28 -2
- package/ROADMAP.md +31 -1
- package/client/studio-client.js +228 -50
- package/index.ts +914 -220
- package/package.json +1 -1
- package/shared/REPL_SESSION_RECORD_PROTOCOL.md +93 -0
- package/shared/repl-control-files.js +158 -0
- package/shared/repl-session-record.js +623 -0
- package/shared/repl-submission-display.js +227 -0
package/client/studio-client.js
CHANGED
|
@@ -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,15 +596,28 @@
|
|
|
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;
|
|
609
|
+
const hasCompatibleOrigin = entry.origin === "pi-repl" || entry.origin === "pi-studio";
|
|
610
|
+
const sharedSynced = entry.sharedSynced === true || (entry.sharedSynced !== false && hasCompatibleOrigin);
|
|
600
611
|
const normalized = {
|
|
601
612
|
id: typeof entry.id === "string" && entry.id ? entry.id : ("repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8)),
|
|
602
613
|
requestId: typeof entry.requestId === "string" ? entry.requestId : "",
|
|
603
614
|
createdAt: typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt) ? entry.createdAt : Date.now(),
|
|
604
615
|
updatedAt: typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt) ? entry.updatedAt : Date.now(),
|
|
616
|
+
completedAt: typeof entry.completedAt === "number" && Number.isFinite(entry.completedAt) ? entry.completedAt : null,
|
|
605
617
|
sessionName: typeof entry.sessionName === "string" ? entry.sessionName : "",
|
|
606
618
|
runtime: typeof entry.runtime === "string" ? entry.runtime : "python",
|
|
619
|
+
origin: hasCompatibleOrigin ? entry.origin : "",
|
|
620
|
+
legacyLocal: !sharedSynced,
|
|
607
621
|
label: typeof entry.label === "string" ? entry.label : "REPL send",
|
|
608
622
|
mode: typeof entry.mode === "string" ? entry.mode : "raw",
|
|
609
623
|
prose: typeof entry.prose === "string" ? entry.prose : "",
|
|
@@ -635,8 +649,11 @@
|
|
|
635
649
|
requestId: entry.requestId,
|
|
636
650
|
createdAt: entry.createdAt,
|
|
637
651
|
updatedAt: entry.updatedAt,
|
|
652
|
+
completedAt: entry.completedAt,
|
|
638
653
|
sessionName: entry.sessionName,
|
|
639
654
|
runtime: entry.runtime,
|
|
655
|
+
origin: entry.origin,
|
|
656
|
+
sharedSynced: !entry.legacyLocal,
|
|
640
657
|
label: entry.label,
|
|
641
658
|
mode: entry.mode,
|
|
642
659
|
prose: entry.prose,
|
|
@@ -671,6 +688,7 @@
|
|
|
671
688
|
mode: existing.mode || entry.mode,
|
|
672
689
|
prose: existing.prose || entry.prose,
|
|
673
690
|
beforeTranscript: existing.beforeTranscript || "",
|
|
691
|
+
legacyLocal: entry.legacyLocal,
|
|
674
692
|
createdAt: existing.createdAt || entry.createdAt,
|
|
675
693
|
updatedAt: Math.max(existing.updatedAt || 0, entry.updatedAt || 0),
|
|
676
694
|
skippedChunks: existing.skippedChunks || entry.skippedChunks,
|
|
@@ -694,6 +712,7 @@
|
|
|
694
712
|
|
|
695
713
|
let replJournalEntries = loadPersistedReplJournalEntries();
|
|
696
714
|
let activeReplJournalEntryId = "";
|
|
715
|
+
const replJournalImportPendingSessions = new Set();
|
|
697
716
|
let replJournalCollapsed = (() => {
|
|
698
717
|
try {
|
|
699
718
|
const stored = window.localStorage ? window.localStorage.getItem("piStudio.replStudioCollapsed") : null;
|
|
@@ -1343,6 +1362,11 @@
|
|
|
1343
1362
|
return String(value || "").trim().toLowerCase() === "literate" ? "literate" : "raw";
|
|
1344
1363
|
}
|
|
1345
1364
|
|
|
1365
|
+
function normalizeReplEchoMode(value) {
|
|
1366
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
1367
|
+
return normalized === "summary" || normalized === "full" ? normalized : "off";
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1346
1370
|
function isMacShortcutPlatform() {
|
|
1347
1371
|
try {
|
|
1348
1372
|
const platform = String((navigator && navigator.platform) || "");
|
|
@@ -1384,6 +1408,28 @@
|
|
|
1384
1408
|
}
|
|
1385
1409
|
}
|
|
1386
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
|
+
|
|
1387
1433
|
function setReplJournalCollapsed(collapsed) {
|
|
1388
1434
|
replJournalCollapsed = Boolean(collapsed);
|
|
1389
1435
|
try {
|
|
@@ -1815,13 +1861,17 @@
|
|
|
1815
1861
|
}
|
|
1816
1862
|
|
|
1817
1863
|
function createReplJournalEntry(details) {
|
|
1864
|
+
const sharedSynced = details.sharedSynced === true;
|
|
1818
1865
|
return {
|
|
1819
1866
|
id: "repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8),
|
|
1820
1867
|
requestId: details.requestId || "",
|
|
1821
1868
|
createdAt: Date.now(),
|
|
1822
1869
|
updatedAt: Date.now(),
|
|
1870
|
+
completedAt: typeof details.completedAt === "number" ? details.completedAt : null,
|
|
1823
1871
|
sessionName: details.sessionName || "",
|
|
1824
1872
|
runtime: details.runtime || getActiveReplRuntime(),
|
|
1873
|
+
origin: details.origin === "pi-repl" ? "pi-repl" : "pi-studio",
|
|
1874
|
+
legacyLocal: !sharedSynced,
|
|
1825
1875
|
label: details.label || "REPL send",
|
|
1826
1876
|
mode: details.mode || replSendMode,
|
|
1827
1877
|
prose: String(details.prose || ""),
|
|
@@ -1840,6 +1890,65 @@
|
|
|
1840
1890
|
return entry;
|
|
1841
1891
|
}
|
|
1842
1892
|
|
|
1893
|
+
function syncReplJournalEntry(entry) {
|
|
1894
|
+
if (!entry || !entry.sessionName || wsState === "Disconnected") return false;
|
|
1895
|
+
return sendMessage({
|
|
1896
|
+
type: "repl_journal_upsert_request",
|
|
1897
|
+
requestId: makeRequestId(),
|
|
1898
|
+
sessionName: entry.sessionName,
|
|
1899
|
+
entry: {
|
|
1900
|
+
id: entry.id,
|
|
1901
|
+
requestId: entry.requestId,
|
|
1902
|
+
createdAt: entry.createdAt,
|
|
1903
|
+
updatedAt: entry.updatedAt,
|
|
1904
|
+
completedAt: entry.completedAt,
|
|
1905
|
+
runtime: entry.runtime,
|
|
1906
|
+
origin: "pi-studio",
|
|
1907
|
+
label: entry.label,
|
|
1908
|
+
mode: entry.mode,
|
|
1909
|
+
prose: entry.prose,
|
|
1910
|
+
code: entry.code,
|
|
1911
|
+
output: entry.output,
|
|
1912
|
+
status: entry.status,
|
|
1913
|
+
skippedChunks: entry.skippedChunks,
|
|
1914
|
+
},
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function maybeImportLegacyReplJournalEntries(sessionName) {
|
|
1919
|
+
const name = String(sessionName || "").trim();
|
|
1920
|
+
if (!name || replJournalImportPendingSessions.has(name) || wsState === "Disconnected") return false;
|
|
1921
|
+
const entries = replJournalEntries.filter((entry) => entry.sessionName === name && entry.legacyLocal).slice(-80);
|
|
1922
|
+
if (!entries.length) return false;
|
|
1923
|
+
replJournalImportPendingSessions.add(name);
|
|
1924
|
+
const sent = sendMessage({
|
|
1925
|
+
type: "repl_journal_import_request",
|
|
1926
|
+
requestId: makeRequestId(),
|
|
1927
|
+
sessionName: name,
|
|
1928
|
+
entries: entries.map((entry) => ({
|
|
1929
|
+
id: entry.id,
|
|
1930
|
+
requestId: entry.requestId,
|
|
1931
|
+
createdAt: entry.createdAt,
|
|
1932
|
+
updatedAt: entry.updatedAt,
|
|
1933
|
+
runtime: entry.runtime,
|
|
1934
|
+
origin: "pi-studio",
|
|
1935
|
+
label: entry.label,
|
|
1936
|
+
mode: entry.mode,
|
|
1937
|
+
prose: entry.prose,
|
|
1938
|
+
code: entry.code,
|
|
1939
|
+
output: entry.output,
|
|
1940
|
+
status: entry.status,
|
|
1941
|
+
skippedChunks: entry.skippedChunks,
|
|
1942
|
+
})),
|
|
1943
|
+
});
|
|
1944
|
+
if (!sent) {
|
|
1945
|
+
replJournalImportPendingSessions.delete(name);
|
|
1946
|
+
} else {
|
|
1947
|
+
window.setTimeout(() => replJournalImportPendingSessions.delete(name), 5000);
|
|
1948
|
+
}
|
|
1949
|
+
return sent;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1843
1952
|
function recordReplToolSend(message) {
|
|
1844
1953
|
const requestId = typeof message.toolCallId === "string" && message.toolCallId.trim()
|
|
1845
1954
|
? "tool:" + message.toolCallId.trim()
|
|
@@ -1853,6 +1962,8 @@
|
|
|
1853
1962
|
requestId,
|
|
1854
1963
|
sessionName,
|
|
1855
1964
|
runtime,
|
|
1965
|
+
origin: message.origin === "pi-repl" ? "pi-repl" : "pi-studio",
|
|
1966
|
+
sharedSynced: message.sharedSynced === true,
|
|
1856
1967
|
label: typeof message.label === "string" && message.label.trim() ? message.label.trim() : "Pi",
|
|
1857
1968
|
mode: "agent",
|
|
1858
1969
|
code,
|
|
@@ -1908,14 +2019,15 @@
|
|
|
1908
2019
|
function stripStudioReplSubmissionEcho(delta) {
|
|
1909
2020
|
let value = String(delta || "").replace(/^\s+/, "");
|
|
1910
2021
|
// The raw mirror below remains raw; Studio record cards hide only the
|
|
1911
|
-
// temp-file wrapper used to submit multiline snippets safely.
|
|
1912
|
-
//
|
|
2022
|
+
// temp-file wrapper used to submit multiline snippets safely. Match
|
|
2023
|
+
// both legacy long Studio roots and compact private control roots.
|
|
1913
2024
|
const submissionEchoPatterns = [
|
|
1914
|
-
/^.*exec\(open\([\s\S]*?pi-studio-re[\s\S]*?globals\(\)\)\s*$/gm,
|
|
1915
|
-
/^.*include\([\s\S]*?pi-studio-re[\s\S]*?\.jl"\)\s*$/gm,
|
|
1916
|
-
/^.*source\([\s\S]*?pi-studio-re[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
1917
|
-
/^.*:script\s+[\s\S]*?pi-studio-re[\s\S]*?\.ghci"?\s*$/gm,
|
|
1918
|
-
/^.*\(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,
|
|
1919
2031
|
];
|
|
1920
2032
|
for (const pattern of submissionEchoPatterns) {
|
|
1921
2033
|
value = value.replace(pattern, "");
|
|
@@ -1951,6 +2063,10 @@
|
|
|
1951
2063
|
if (entryIndex < 0) return false;
|
|
1952
2064
|
const entry = replJournalEntries[entryIndex];
|
|
1953
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
|
+
}
|
|
1954
2070
|
const delta = cleanReplCapturedOutput(extractReplTranscriptDelta(entry.beforeTranscript, transcript), entry);
|
|
1955
2071
|
if (!delta.trim()) return false;
|
|
1956
2072
|
if (entry.output === delta && entry.status === "captured") return false;
|
|
@@ -1971,19 +2087,28 @@
|
|
|
1971
2087
|
function buildReplJournalMarkdown(entries) {
|
|
1972
2088
|
const visibleEntries = Array.isArray(entries) ? entries : getVisibleReplJournalEntries();
|
|
1973
2089
|
const sessionName = getActiveReplJournalSessionName();
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
2090
|
+
const updatedAt = visibleEntries.reduce((latest, entry) => Math.max(latest, Number(entry.updatedAt) || 0), 0) || Date.now();
|
|
2091
|
+
const lines = [
|
|
2092
|
+
"# Shared REPL Record",
|
|
2093
|
+
"",
|
|
2094
|
+
"Session: `" + (sessionName || "unknown") + "`",
|
|
2095
|
+
"Record protocol: pi-repl-session-record v1",
|
|
2096
|
+
"Updated: " + new Date(updatedAt).toISOString(),
|
|
2097
|
+
"",
|
|
2098
|
+
];
|
|
1977
2099
|
if (!visibleEntries.length) {
|
|
1978
|
-
lines.push(
|
|
2100
|
+
lines.push("_No compatible-client entries have been recorded for this tmux session._", "");
|
|
2101
|
+
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
2102
|
return lines.join("\n");
|
|
1980
2103
|
}
|
|
1981
2104
|
visibleEntries.forEach((entry, index) => {
|
|
1982
2105
|
lines.push("## " + (index + 1) + ". " + (entry.label || "REPL entry"));
|
|
1983
2106
|
lines.push("");
|
|
1984
|
-
lines.push("- Time: " + new Date(entry.createdAt || Date.now()).
|
|
2107
|
+
lines.push("- Time: " + new Date(entry.createdAt || Date.now()).toISOString());
|
|
2108
|
+
lines.push("- Origin: " + (entry.origin || "unknown"));
|
|
2109
|
+
lines.push("- Mode: " + (entry.mode || "raw"));
|
|
2110
|
+
lines.push("- Status: " + (entry.status || "sent"));
|
|
1985
2111
|
if (entry.runtime) lines.push("- Runtime: " + entry.runtime);
|
|
1986
|
-
if (entry.sessionName) lines.push("- Session: `" + entry.sessionName + "`");
|
|
1987
2112
|
if (entry.skippedChunks) lines.push("- Skipped chunks: " + entry.skippedChunks);
|
|
1988
2113
|
lines.push("");
|
|
1989
2114
|
if (String(entry.prose || "").trim()) {
|
|
@@ -1991,7 +2116,10 @@
|
|
|
1991
2116
|
lines.push("");
|
|
1992
2117
|
}
|
|
1993
2118
|
if (String(entry.code || "").trim()) {
|
|
1994
|
-
|
|
2119
|
+
const markdownRuntime = entry.runtime === "ipython"
|
|
2120
|
+
? "python"
|
|
2121
|
+
: (entry.runtime === "unknown" || entry.runtime === "shell" ? "" : entry.runtime);
|
|
2122
|
+
lines.push(getMarkdownFenceForText(entry.code, markdownRuntime));
|
|
1995
2123
|
lines.push("");
|
|
1996
2124
|
}
|
|
1997
2125
|
if (String(entry.output || "").trim()) {
|
|
@@ -2001,17 +2129,18 @@
|
|
|
2001
2129
|
lines.push("");
|
|
2002
2130
|
}
|
|
2003
2131
|
});
|
|
2132
|
+
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
2133
|
return lines.join("\n").replace(/\n{4,}/g, "\n\n\n").trimEnd() + "\n";
|
|
2005
2134
|
}
|
|
2006
2135
|
|
|
2007
2136
|
async function copyReplJournalToClipboard() {
|
|
2008
2137
|
const entries = getVisibleReplJournalEntries();
|
|
2009
2138
|
if (!entries.length) {
|
|
2010
|
-
setStatus("No
|
|
2139
|
+
setStatus("No shared REPL record entries to copy for this session yet.", "warning");
|
|
2011
2140
|
return;
|
|
2012
2141
|
}
|
|
2013
2142
|
if (await writeTextToClipboard(buildReplJournalMarkdown(entries))) {
|
|
2014
|
-
setStatus("Copied
|
|
2143
|
+
setStatus("Copied shared REPL record as Markdown.", "success");
|
|
2015
2144
|
} else {
|
|
2016
2145
|
setStatus("Clipboard write failed.", "warning");
|
|
2017
2146
|
}
|
|
@@ -2020,7 +2149,7 @@
|
|
|
2020
2149
|
function exportReplJournalMarkdown() {
|
|
2021
2150
|
const entries = getVisibleReplJournalEntries();
|
|
2022
2151
|
if (!entries.length) {
|
|
2023
|
-
setStatus("No
|
|
2152
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
2024
2153
|
return;
|
|
2025
2154
|
}
|
|
2026
2155
|
const blob = new Blob([buildReplJournalMarkdown(entries)], { type: "text/markdown;charset=utf-8" });
|
|
@@ -2029,12 +2158,12 @@
|
|
|
2029
2158
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2030
2159
|
const sessionSlug = getActiveReplJournalSessionName().replace(/[^-_.A-Za-z0-9]+/g, "-");
|
|
2031
2160
|
link.href = blobUrl;
|
|
2032
|
-
link.download = "repl-
|
|
2161
|
+
link.download = "repl-record" + (sessionSlug ? "-" + sessionSlug : "") + "-" + stamp + ".md";
|
|
2033
2162
|
document.body.appendChild(link);
|
|
2034
2163
|
link.click();
|
|
2035
2164
|
link.remove();
|
|
2036
2165
|
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
|
|
2037
|
-
setStatus("Exported
|
|
2166
|
+
setStatus("Exported shared REPL record Markdown.", "success");
|
|
2038
2167
|
}
|
|
2039
2168
|
|
|
2040
2169
|
function clearReplJournal() {
|
|
@@ -2046,30 +2175,33 @@
|
|
|
2046
2175
|
}
|
|
2047
2176
|
activeReplJournalEntryId = "";
|
|
2048
2177
|
persistReplJournalEntries();
|
|
2049
|
-
|
|
2178
|
+
if (sessionName) {
|
|
2179
|
+
sendMessage({ type: "repl_journal_clear_request", requestId: makeRequestId(), sessionName });
|
|
2180
|
+
}
|
|
2181
|
+
setStatus(sessionName ? "Cleared shared REPL record for this session." : "Cleared local REPL record cache.", "success");
|
|
2050
2182
|
renderReplViewIfActive({ force: true });
|
|
2051
2183
|
}
|
|
2052
2184
|
|
|
2053
2185
|
function loadReplJournalIntoEditor() {
|
|
2054
2186
|
const entries = getVisibleReplJournalEntries();
|
|
2055
2187
|
if (!entries.length) {
|
|
2056
|
-
setStatus("No
|
|
2188
|
+
setStatus("No shared REPL record entries to load for this session yet.", "warning");
|
|
2057
2189
|
return;
|
|
2058
2190
|
}
|
|
2059
2191
|
const markdown = buildReplJournalMarkdown(entries);
|
|
2060
2192
|
setEditorText(markdown, { preserveScroll: false, preserveSelection: false });
|
|
2061
|
-
setSourceState({ source: "blank", label: "
|
|
2193
|
+
setSourceState({ source: "blank", label: "Shared REPL Record", path: null });
|
|
2062
2194
|
setEditorLanguage("markdown");
|
|
2063
|
-
setStatus("Loaded
|
|
2195
|
+
setStatus("Loaded shared REPL record into editor.", "success");
|
|
2064
2196
|
}
|
|
2065
2197
|
|
|
2066
2198
|
function addSelectedReplJournalNote() {
|
|
2067
2199
|
const note = getSelectedOrCurrentParagraphForReplNote();
|
|
2068
2200
|
if (!note.trim()) {
|
|
2069
|
-
setStatus("Select prose or place the cursor in a paragraph to add a
|
|
2201
|
+
setStatus("Select prose or place the cursor in a paragraph to add a shared REPL record note.", "warning");
|
|
2070
2202
|
return;
|
|
2071
2203
|
}
|
|
2072
|
-
addReplJournalEntry({
|
|
2204
|
+
const entry = addReplJournalEntry({
|
|
2073
2205
|
label: "note",
|
|
2074
2206
|
prose: note,
|
|
2075
2207
|
status: "note",
|
|
@@ -2077,7 +2209,8 @@
|
|
|
2077
2209
|
sessionName: replActiveSessionName,
|
|
2078
2210
|
runtime: getActiveReplRuntime(),
|
|
2079
2211
|
});
|
|
2080
|
-
|
|
2212
|
+
syncReplJournalEntry(entry);
|
|
2213
|
+
setStatus("Added note to shared REPL record.", "success");
|
|
2081
2214
|
renderReplViewIfActive({ force: true });
|
|
2082
2215
|
}
|
|
2083
2216
|
|
|
@@ -2093,7 +2226,7 @@
|
|
|
2093
2226
|
}
|
|
2094
2227
|
if (payload.noteOnly) {
|
|
2095
2228
|
if (String(payload.prose || "").trim()) {
|
|
2096
|
-
addReplJournalEntry({
|
|
2229
|
+
const entry = addReplJournalEntry({
|
|
2097
2230
|
label: payload.label || "note",
|
|
2098
2231
|
prose: payload.prose,
|
|
2099
2232
|
status: "note",
|
|
@@ -2102,7 +2235,8 @@
|
|
|
2102
2235
|
runtime: getActiveReplRuntime(),
|
|
2103
2236
|
skippedChunks: payload.skippedChunks,
|
|
2104
2237
|
});
|
|
2105
|
-
|
|
2238
|
+
syncReplJournalEntry(entry);
|
|
2239
|
+
setStatus("Added prose to shared REPL record.", "success");
|
|
2106
2240
|
renderReplViewIfActive({ force: true });
|
|
2107
2241
|
} else {
|
|
2108
2242
|
setStatus("No code or prose found to send.", "warning");
|
|
@@ -2133,7 +2267,19 @@
|
|
|
2133
2267
|
renderReplViewIfActive({ force: true });
|
|
2134
2268
|
const skippedSuffix = payload.skippedChunks ? " (skipped " + payload.skippedChunks + " incompatible chunk" + (payload.skippedChunks === 1 ? "" : "s") + ")" : "";
|
|
2135
2269
|
setStatus("Sending " + (payload.label || "editor text") + " to REPL…" + skippedSuffix, "info");
|
|
2136
|
-
if (!sendMessage({
|
|
2270
|
+
if (!sendMessage({
|
|
2271
|
+
type: "repl_send_request",
|
|
2272
|
+
requestId,
|
|
2273
|
+
sessionName: session.sessionName,
|
|
2274
|
+
text,
|
|
2275
|
+
echoMode: replEchoMode,
|
|
2276
|
+
journalEntryId: journalEntry.id,
|
|
2277
|
+
createdAt: journalEntry.createdAt,
|
|
2278
|
+
label: journalEntry.label,
|
|
2279
|
+
mode: journalEntry.mode,
|
|
2280
|
+
prose: journalEntry.prose,
|
|
2281
|
+
skippedChunks: journalEntry.skippedChunks,
|
|
2282
|
+
})) {
|
|
2137
2283
|
replBusy = false;
|
|
2138
2284
|
replJournalEntries = replJournalEntries.map((entry) => entry.id === journalEntry.id ? { ...entry, status: "error" } : entry);
|
|
2139
2285
|
persistReplJournalEntries();
|
|
@@ -10223,7 +10369,7 @@
|
|
|
10223
10369
|
}
|
|
10224
10370
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
10225
10371
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
10226
|
-
setStatus("No
|
|
10372
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
10227
10373
|
return;
|
|
10228
10374
|
}
|
|
10229
10375
|
|
|
@@ -10468,7 +10614,7 @@
|
|
|
10468
10614
|
}
|
|
10469
10615
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
10470
10616
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
10471
|
-
setStatus("No
|
|
10617
|
+
setStatus("No shared REPL record entries to export for this session yet.", "warning");
|
|
10472
10618
|
return;
|
|
10473
10619
|
}
|
|
10474
10620
|
|
|
@@ -10501,7 +10647,7 @@
|
|
|
10501
10647
|
let filenameHint = exportingSideThread
|
|
10502
10648
|
? getSideQuestionTranscriptFilename(sideThreadExportedAt).replace(/\.md$/i, ".html")
|
|
10503
10649
|
: (exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.html")));
|
|
10504
|
-
let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "
|
|
10650
|
+
let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "Shared REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview"));
|
|
10505
10651
|
if (sourcePath) {
|
|
10506
10652
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
10507
10653
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
@@ -11479,6 +11625,7 @@
|
|
|
11479
11625
|
const parts = [];
|
|
11480
11626
|
const kind = getReplStudioEntryKind(entry);
|
|
11481
11627
|
if (kind !== "Raw") parts.push(kind);
|
|
11628
|
+
if (entry.origin) parts.push(entry.origin);
|
|
11482
11629
|
const time = formatReferenceTime(entry.createdAt);
|
|
11483
11630
|
if (time) parts.push(time);
|
|
11484
11631
|
if (entry.skippedChunks) parts.push("skipped " + String(entry.skippedChunks));
|
|
@@ -11532,12 +11679,12 @@
|
|
|
11532
11679
|
const toggleButton = "<button type='button' data-repl-action='journal-toggle' aria-expanded='" + (replJournalCollapsed ? "false" : "true") + "'>" + (replJournalCollapsed ? "Show record" : "Hide record") + "</button>";
|
|
11533
11680
|
const toggleActions = "<div class='repl-journal-actions'>" + toggleButton + "</div>";
|
|
11534
11681
|
const summaryText = hasEntries
|
|
11535
|
-
? (entryCount + "
|
|
11536
|
-
: (sessionName ? "No
|
|
11682
|
+
? (entryCount + " shared record entr" + (entryCount === 1 ? "y" : "ies") + (sessionName ? " for " + sessionName : "") + ". Export is Markdown.")
|
|
11683
|
+
: (sessionName ? "No compatible-client entries for " + sessionName + "." : "pi-repl and pi-studio submissions will appear here.");
|
|
11537
11684
|
if (replJournalCollapsed) {
|
|
11538
11685
|
return "<section class='repl-journal repl-journal-compact" + collapsedClass + "'>"
|
|
11539
11686
|
+ "<div class='repl-journal-compact-row'>"
|
|
11540
|
-
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>
|
|
11687
|
+
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>Shared REPL Record</span><span>" + escapeHtml(summaryText) + "</span></div>"
|
|
11541
11688
|
+ "<div class='repl-journal-actions'>" + toggleButton + "</div>"
|
|
11542
11689
|
+ "</div>"
|
|
11543
11690
|
+ "</section>";
|
|
@@ -11575,14 +11722,14 @@
|
|
|
11575
11722
|
}).join("");
|
|
11576
11723
|
const emptyText = sessionName
|
|
11577
11724
|
? (String(transcript || "").trim()
|
|
11578
|
-
? "No
|
|
11579
|
-
: "No
|
|
11580
|
-
: "No
|
|
11725
|
+
? "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."
|
|
11726
|
+
: "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.")
|
|
11727
|
+
: "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.";
|
|
11581
11728
|
const terminalContent = banner
|
|
11582
11729
|
+ (hasEntries ? cards : "<div class='repl-studio-empty'>" + escapeHtml(emptyText) + "</div>");
|
|
11583
11730
|
return "<section class='repl-journal'>"
|
|
11584
|
-
+ "<div class='repl-journal-header'><h3>
|
|
11585
|
-
+ "<p class='repl-journal-description'>Clean record for
|
|
11731
|
+
+ "<div class='repl-journal-header'><h3>Shared REPL Record</h3>" + toggleActions + "</div>"
|
|
11732
|
+
+ "<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
11733
|
+ (omitted ? "<div class='repl-journal-omitted'>Showing latest 12 entries for this session; " + escapeHtml(String(omitted)) + " older entries remain in export.</div>" : "")
|
|
11587
11734
|
+ "<div class='repl-journal-list'>" + terminalContent + "</div>"
|
|
11588
11735
|
+ "</section>";
|
|
@@ -11606,7 +11753,7 @@
|
|
|
11606
11753
|
+ "</section>";
|
|
11607
11754
|
}
|
|
11608
11755
|
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
|
|
11756
|
+
+ "<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
11757
|
+ body
|
|
11611
11758
|
+ "</section>";
|
|
11612
11759
|
}
|
|
@@ -11656,7 +11803,7 @@
|
|
|
11656
11803
|
+ "<button type='button' data-repl-action='interrupt'" + (activeSession && !replBusy ? "" : " disabled") + " title='Send Ctrl+C to the active REPL session.'>Interrupt</button>"
|
|
11657
11804
|
+ "<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
11805
|
+ "<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
|
|
11806
|
+
+ "<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
11807
|
+ "<button type='button' data-repl-action='refresh'>Refresh</button>"
|
|
11661
11808
|
+ "<button type='button' data-repl-action='follow'>Follow: " + (replFollow ? "On" : "Off") + "</button>"
|
|
11662
11809
|
+ "</div>"
|
|
@@ -13677,7 +13824,7 @@
|
|
|
13677
13824
|
} else if (exportingSideThread) {
|
|
13678
13825
|
exportPdfBtn.title = "Save or copy Markdown, open it in an editor, or export the visible side discussion as PDF or HTML.";
|
|
13679
13826
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
13680
|
-
exportPdfBtn.title = "No
|
|
13827
|
+
exportPdfBtn.title = "No shared REPL record entries to export for this session yet.";
|
|
13681
13828
|
} else if (rightView === "markdown") {
|
|
13682
13829
|
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export.";
|
|
13683
13830
|
} else if (!canExportPreview) {
|
|
@@ -13685,7 +13832,7 @@
|
|
|
13685
13832
|
} else if (isHtmlArtifactPreview) {
|
|
13686
13833
|
exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
|
|
13687
13834
|
} else if (exportingReplJournal) {
|
|
13688
|
-
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the
|
|
13835
|
+
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the shared REPL record.";
|
|
13689
13836
|
} else {
|
|
13690
13837
|
exportPdfBtn.title = "Choose PDF export or an HTML export destination for the current right-pane preview.";
|
|
13691
13838
|
}
|
|
@@ -13697,7 +13844,7 @@
|
|
|
13697
13844
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13698
13845
|
: (isHtmlArtifactPreview
|
|
13699
13846
|
? "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
|
|
13847
|
+
: (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
13848
|
}
|
|
13702
13849
|
if (exportPreviewPdfBtn) {
|
|
13703
13850
|
exportPreviewPdfBtn.disabled = exportBusy || !canExportPreview || isHtmlArtifactPreview || sideThreadRenderTooLarge;
|
|
@@ -13705,7 +13852,7 @@
|
|
|
13705
13852
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13706
13853
|
: (isHtmlArtifactPreview
|
|
13707
13854
|
? "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
|
|
13855
|
+
: (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
13856
|
}
|
|
13710
13857
|
if (exportPreviewHtmlStudioBtn) {
|
|
13711
13858
|
exportPreviewHtmlStudioBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
@@ -13713,7 +13860,7 @@
|
|
|
13713
13860
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13714
13861
|
: (isHtmlArtifactPreview
|
|
13715
13862
|
? "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
|
|
13863
|
+
: (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
13864
|
}
|
|
13718
13865
|
if (exportPreviewHtmlBtn) {
|
|
13719
13866
|
exportPreviewHtmlBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
|
|
@@ -13721,7 +13868,7 @@
|
|
|
13721
13868
|
? "This transcript is too large for rendered export; save or copy the Markdown instead."
|
|
13722
13869
|
: (isHtmlArtifactPreview
|
|
13723
13870
|
? "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
|
|
13871
|
+
: (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
13872
|
}
|
|
13726
13873
|
if (exportPreviewControlsEl) {
|
|
13727
13874
|
exportPreviewControlsEl.hidden = rightView === "editor-quarto-preview";
|
|
@@ -13729,11 +13876,11 @@
|
|
|
13729
13876
|
? (exportingSideThread
|
|
13730
13877
|
? "Choose a durable export for the visible side discussion."
|
|
13731
13878
|
: (exportingReplJournal
|
|
13732
|
-
? "Choose a format and export destination for the
|
|
13879
|
+
? "Choose a format and export destination for the shared REPL record."
|
|
13733
13880
|
: (isHtmlArtifactPreview ? "Export this HTML preview to Studio or browser." : "Choose a format and export destination for the current right-pane preview.")))
|
|
13734
13881
|
: (exportingSideThread
|
|
13735
13882
|
? "No completed side discussion is available to export yet."
|
|
13736
|
-
: (exportingReplJournal ? "No
|
|
13883
|
+
: (exportingReplJournal ? "No shared REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting."));
|
|
13737
13884
|
}
|
|
13738
13885
|
if (!canExportPreview || previewExportInProgress || sideQuestionMarkdownExportRequest) {
|
|
13739
13886
|
closeExportPreviewMenu();
|
|
@@ -22237,6 +22384,7 @@
|
|
|
22237
22384
|
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
22238
22385
|
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
22239
22386
|
}
|
|
22387
|
+
syncReplEchoModeSelect(showReplSend);
|
|
22240
22388
|
if (critiqueBtn) {
|
|
22241
22389
|
critiqueBtn.textContent = "Critique text";
|
|
22242
22390
|
critiqueBtn.classList.remove("request-stop-active");
|
|
@@ -22299,6 +22447,7 @@
|
|
|
22299
22447
|
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
22300
22448
|
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
22301
22449
|
}
|
|
22450
|
+
syncReplEchoModeSelect(rightView === "repl");
|
|
22302
22451
|
|
|
22303
22452
|
if (critiqueBtn) {
|
|
22304
22453
|
critiqueBtn.textContent = critiqueIsStop ? "Stop" : "Critique text";
|
|
@@ -22911,6 +23060,7 @@
|
|
|
22911
23060
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
22912
23061
|
}
|
|
22913
23062
|
const journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23063
|
+
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
22914
23064
|
if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
|
|
22915
23065
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
22916
23066
|
replError = typeof message.replError === "string" ? message.replError : (typeof message.captureError === "string" ? message.captureError : "");
|
|
@@ -22964,6 +23114,7 @@
|
|
|
22964
23114
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
22965
23115
|
}
|
|
22966
23116
|
let journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23117
|
+
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
22967
23118
|
if (typeof message.transcript === "string") {
|
|
22968
23119
|
replTranscript = trimReplTranscript(message.transcript);
|
|
22969
23120
|
journalChanged = updateActiveReplJournalEntryFromTranscript(
|
|
@@ -22988,6 +23139,19 @@
|
|
|
22988
23139
|
return;
|
|
22989
23140
|
}
|
|
22990
23141
|
|
|
23142
|
+
if (message.type === "repl_journal_ack") {
|
|
23143
|
+
const sessionName = typeof message.sessionName === "string" ? message.sessionName : "";
|
|
23144
|
+
if (sessionName) replJournalImportPendingSessions.delete(sessionName);
|
|
23145
|
+
if (message.cleared && sessionName) {
|
|
23146
|
+
replJournalEntries = replJournalEntries.filter((entry) => entry.sessionName !== sessionName);
|
|
23147
|
+
persistReplJournalEntries();
|
|
23148
|
+
} else {
|
|
23149
|
+
mergeReplJournalEntries(message.journalEntries);
|
|
23150
|
+
}
|
|
23151
|
+
renderReplViewIfActive({ force: true });
|
|
23152
|
+
return;
|
|
23153
|
+
}
|
|
23154
|
+
|
|
22991
23155
|
if (message.type === "repl_send_ack") {
|
|
22992
23156
|
replBusy = false;
|
|
22993
23157
|
replMessage = "";
|
|
@@ -24840,6 +25004,19 @@
|
|
|
24840
25004
|
});
|
|
24841
25005
|
}
|
|
24842
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
|
+
|
|
24843
25020
|
copyDraftBtn.addEventListener("click", async () => {
|
|
24844
25021
|
const content = sourceTextEl.value;
|
|
24845
25022
|
if (!content.trim()) {
|
|
@@ -25460,6 +25637,7 @@
|
|
|
25460
25637
|
const initialAnnotationsEnabled = storedAnnotationsEnabled ?? Boolean(annotationModeSelect ? annotationModeSelect.value !== "off" : true);
|
|
25461
25638
|
setAnnotationsEnabled(initialAnnotationsEnabled, { silent: true });
|
|
25462
25639
|
setReplSendMode(replSendMode);
|
|
25640
|
+
setReplEchoMode(replEchoMode);
|
|
25463
25641
|
|
|
25464
25642
|
const sessionWorkspaceState = readPersistedWorkspaceState();
|
|
25465
25643
|
const serverWorkspaceRecovery = await readServerWorkspaceRecoveryState();
|