pi-studio 0.9.56 → 0.9.58
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 +32 -0
- package/README.md +28 -7
- package/ROADMAP.md +26 -2
- package/client/studio-client.js +587 -57
- package/client/studio.css +171 -0
- package/index.ts +249 -114
- package/package.json +1 -1
- package/shared/REPL_SESSION_RECORD_PROTOCOL.md +24 -1
- package/shared/repl-control-files.js +158 -0
- package/shared/repl-submission-display.js +227 -0
package/index.ts
CHANGED
|
@@ -55,6 +55,15 @@ import {
|
|
|
55
55
|
readReplSessionRecord,
|
|
56
56
|
upsertReplSessionRecordEntry,
|
|
57
57
|
} from "./shared/repl-session-record.js";
|
|
58
|
+
import {
|
|
59
|
+
createReplSubmissionDisplay,
|
|
60
|
+
normalizeReplSubmissionEchoMode,
|
|
61
|
+
stripReplSubmissionDisplay,
|
|
62
|
+
} from "./shared/repl-submission-display.js";
|
|
63
|
+
import {
|
|
64
|
+
cleanupPrivateReplControlFiles,
|
|
65
|
+
createPrivateReplControlFiles,
|
|
66
|
+
} from "./shared/repl-control-files.js";
|
|
58
67
|
import { buildStudioForwardingHint, buildStudioSshTunnelHint, isStudioSshSession as isSshSession } from "./shared/studio-ssh-hint.js";
|
|
59
68
|
import {
|
|
60
69
|
buildStudioPendingPage,
|
|
@@ -127,6 +136,8 @@ type TerminalActivityPhase = "idle" | "running" | "tool" | "responding";
|
|
|
127
136
|
type StudioPromptMode = "response" | "run" | "effective";
|
|
128
137
|
type StudioPromptTriggerKind = "run" | "steer";
|
|
129
138
|
type StudioReplRuntime = "shell" | "python" | "ipython" | "julia" | "r" | "ghci" | "clojure";
|
|
139
|
+
type ReplSubmissionEchoMode = "off" | "summary" | "full";
|
|
140
|
+
type ReplSubmissionDisplay = ReturnType<typeof createReplSubmissionDisplay>;
|
|
130
141
|
type StudioQuizAngle = "general" | "scientist" | "mathematician" | "statistician" | "developer" | "reviewer";
|
|
131
142
|
type StudioQuizScope = "selection" | "editor" | "file" | "folder" | "repo";
|
|
132
143
|
type StudioQuizThinking = "off" | "minimal" | "low" | "medium" | "high";
|
|
@@ -746,6 +757,7 @@ interface ReplSendRequestMessage {
|
|
|
746
757
|
requestId: string;
|
|
747
758
|
sessionName: string;
|
|
748
759
|
text: string;
|
|
760
|
+
echoMode?: ReplSubmissionEchoMode;
|
|
749
761
|
journalEntryId?: string;
|
|
750
762
|
createdAt?: number;
|
|
751
763
|
label?: string;
|
|
@@ -968,7 +980,6 @@ const STUDIO_REPL_SEND_DEFAULT_TIMEOUT_MS = 20_000;
|
|
|
968
980
|
const STUDIO_REPL_SEND_MAX_TIMEOUT_MS = 120_000;
|
|
969
981
|
const STUDIO_REPL_JOURNAL_MAX_ENTRIES = 300;
|
|
970
982
|
const STUDIO_REPL_RUNTIME_OPTION = "@pi_repl_runtime";
|
|
971
|
-
const STUDIO_REPL_CONTROL_ROOT = join(tmpdir(), "pi-studio-repl");
|
|
972
983
|
const STUDIO_SUBPROCESS_OUTPUT_MAX_BYTES = 2_000_000;
|
|
973
984
|
const STUDIO_PANDOC_TIMEOUT_MS = readStudioPositiveEnvMs("PI_STUDIO_PANDOC_TIMEOUT_MS", 120_000, 5_000, 15 * 60_000);
|
|
974
985
|
const STUDIO_LATEX_TIMEOUT_MS = readStudioPositiveEnvMs("PI_STUDIO_LATEX_TIMEOUT_MS", 120_000, 5_000, 15 * 60_000);
|
|
@@ -988,7 +999,12 @@ const STUDIO_REPL_SEND_TOOL_PARAMS = Type.Object({
|
|
|
988
999
|
sessionName: Type.Optional(Type.String({ description: "Exact Studio/pi-repl tmux session name. If omitted, Studio uses the active REPL session, or the first session matching target." })),
|
|
989
1000
|
target: Type.Optional(Type.String({ description: "Optional runtime target: shell, python, ipython, julia, r, ghci, or clojure. Used when sessionName is omitted." })),
|
|
990
1001
|
timeoutMs: Type.Optional(Type.Number({ description: "Maximum time to wait for completion when Studio can detect it (default 20000, max 120000).", minimum: 1000, maximum: STUDIO_REPL_SEND_MAX_TIMEOUT_MS })),
|
|
1002
|
+
echoMode: Type.Optional(Type.Union(
|
|
1003
|
+
[Type.Literal("off"), Type.Literal("summary"), Type.Literal("full")],
|
|
1004
|
+
{ description: "How much submitted code to echo visibly in the raw REPL pane. Defaults to PI_STUDIO_REPL_ECHO_MODE or off. Summary shows short submissions in full and truncates longer ones; Full has larger bounds and writes source code into persistent raw terminal history." },
|
|
1005
|
+
)),
|
|
991
1006
|
});
|
|
1007
|
+
const STUDIO_REPL_TOOL_ECHO_MODE = normalizeReplSubmissionEchoMode(process.env.PI_STUDIO_REPL_ECHO_MODE) as ReplSubmissionEchoMode;
|
|
992
1008
|
const STUDIO_REPL_STATUS_TOOL_PARAMS = Type.Object({
|
|
993
1009
|
sessionName: Type.Optional(Type.String({ description: "Exact Studio/pi-repl tmux session name to inspect." })),
|
|
994
1010
|
target: Type.Optional(Type.String({ description: "Optional runtime target: shell, python, ipython, julia, r, ghci, or clojure. If omitted, report all Studio-visible REPL sessions." })),
|
|
@@ -10064,6 +10080,7 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
10064
10080
|
requestId: msg.requestId,
|
|
10065
10081
|
sessionName: msg.sessionName,
|
|
10066
10082
|
text: msg.text,
|
|
10083
|
+
echoMode: normalizeReplSubmissionEchoMode(msg.echoMode) as ReplSubmissionEchoMode,
|
|
10067
10084
|
journalEntryId: typeof msg.journalEntryId === "string" ? msg.journalEntryId.slice(0, 240) : undefined,
|
|
10068
10085
|
createdAt: typeof msg.createdAt === "number" && Number.isFinite(msg.createdAt) ? msg.createdAt : undefined,
|
|
10069
10086
|
label: typeof msg.label === "string" ? msg.label.slice(0, 240) : undefined,
|
|
@@ -11159,7 +11176,9 @@ type StudioReplPreparedSubmission = {
|
|
|
11159
11176
|
runtime: StudioReplRuntime | "unknown";
|
|
11160
11177
|
usedControlFile: boolean;
|
|
11161
11178
|
submissionText: string;
|
|
11179
|
+
completionLine?: string;
|
|
11162
11180
|
controlFiles?: StudioReplControlFiles;
|
|
11181
|
+
display?: ReplSubmissionDisplay;
|
|
11163
11182
|
};
|
|
11164
11183
|
|
|
11165
11184
|
type StudioReplSendSuccess = {
|
|
@@ -11168,7 +11187,9 @@ type StudioReplSendSuccess = {
|
|
|
11168
11187
|
runtime: StudioReplRuntime | "unknown";
|
|
11169
11188
|
usedControlFile: boolean;
|
|
11170
11189
|
submissionText: string;
|
|
11190
|
+
completionLine?: string;
|
|
11171
11191
|
controlFiles?: StudioReplControlFiles;
|
|
11192
|
+
display?: ReplSubmissionDisplay;
|
|
11172
11193
|
};
|
|
11173
11194
|
|
|
11174
11195
|
type StudioReplSendFailure = {
|
|
@@ -11178,7 +11199,9 @@ type StudioReplSendFailure = {
|
|
|
11178
11199
|
runtime?: StudioReplRuntime | "unknown";
|
|
11179
11200
|
usedControlFile?: boolean;
|
|
11180
11201
|
submissionText?: string;
|
|
11202
|
+
completionLine?: string;
|
|
11181
11203
|
controlFiles?: StudioReplControlFiles;
|
|
11204
|
+
display?: ReplSubmissionDisplay;
|
|
11182
11205
|
};
|
|
11183
11206
|
|
|
11184
11207
|
function sleep(ms: number): Promise<void> {
|
|
@@ -11194,33 +11217,44 @@ function shellQuote(value: string): string {
|
|
|
11194
11217
|
return `'${String(value || "").replace(/'/g, `'"'"'`)}'`;
|
|
11195
11218
|
}
|
|
11196
11219
|
|
|
11197
|
-
function
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
return
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11220
|
+
function getStudioReplControlExtension(runtime: StudioReplRuntime): string {
|
|
11221
|
+
if (runtime === "julia") return "jl";
|
|
11222
|
+
if (runtime === "r") return "R";
|
|
11223
|
+
if (runtime === "ghci") return "ghci";
|
|
11224
|
+
if (runtime === "clojure") return "clj";
|
|
11225
|
+
if (runtime === "shell") return "sh";
|
|
11226
|
+
return "py";
|
|
11227
|
+
}
|
|
11228
|
+
|
|
11229
|
+
function buildStudioPythonDisplayStatements(display: ReplSubmissionDisplay, indent = ""): string[] {
|
|
11230
|
+
if (!display.enabled) return [];
|
|
11231
|
+
return display.prefixLines.map((line) => `${indent}__pi_studio_builtins.print(${JSON.stringify(line)})`);
|
|
11232
|
+
}
|
|
11233
|
+
|
|
11234
|
+
function buildStudioJuliaDisplayStatements(display: ReplSubmissionDisplay, indent = ""): string[] {
|
|
11235
|
+
if (!display.enabled) return [];
|
|
11236
|
+
return display.prefixLines.map((line) => `${indent}Base.println(${JSON.stringify(line)})`);
|
|
11237
|
+
}
|
|
11238
|
+
|
|
11239
|
+
function buildStudioRDisplayStatements(display: ReplSubmissionDisplay, indent = ""): string[] {
|
|
11240
|
+
if (!display.enabled) return [];
|
|
11241
|
+
return display.prefixLines.map((line) => `${indent}base::cat(${JSON.stringify(`${line}\n`)})`);
|
|
11242
|
+
}
|
|
11243
|
+
|
|
11244
|
+
function buildStudioClojureDisplayStatements(display: ReplSubmissionDisplay, indent = ""): string[] {
|
|
11245
|
+
if (!display.enabled) return [];
|
|
11246
|
+
return display.prefixLines.map((line) => `${indent}(clojure.core/println ${JSON.stringify(line)})`);
|
|
11217
11247
|
}
|
|
11218
11248
|
|
|
11219
|
-
function buildStudioPythonControlSource(runtime: "python" | "ipython", code: string, doneFile: string): string {
|
|
11249
|
+
function buildStudioPythonControlSource(runtime: "python" | "ipython", code: string, doneFile: string, display: ReplSubmissionDisplay): string {
|
|
11250
|
+
const prefix = buildStudioPythonDisplayStatements(display);
|
|
11251
|
+
const completion = display.enabled ? [` __pi_studio_builtins.print(${JSON.stringify(display.endMarker)})`] : [];
|
|
11220
11252
|
if (runtime === "ipython") {
|
|
11221
11253
|
return [
|
|
11222
11254
|
"from pathlib import Path as __pi_studio_path",
|
|
11255
|
+
"import builtins as __pi_studio_builtins",
|
|
11223
11256
|
"import traceback as __pi_studio_traceback",
|
|
11257
|
+
...prefix,
|
|
11224
11258
|
"try:",
|
|
11225
11259
|
" __pi_studio_ip = get_ipython()",
|
|
11226
11260
|
" if __pi_studio_ip is None:",
|
|
@@ -11231,14 +11265,17 @@ function buildStudioPythonControlSource(runtime: "python" | "ipython", code: str
|
|
|
11231
11265
|
"except Exception:",
|
|
11232
11266
|
" __pi_studio_traceback.print_exc()",
|
|
11233
11267
|
"finally:",
|
|
11268
|
+
...completion,
|
|
11234
11269
|
` __pi_studio_path(${JSON.stringify(doneFile)}).write_text('done\\n', encoding='utf-8')`,
|
|
11235
11270
|
].join("\n");
|
|
11236
11271
|
}
|
|
11237
11272
|
|
|
11238
11273
|
return [
|
|
11239
11274
|
"from pathlib import Path as __pi_studio_path",
|
|
11275
|
+
"import builtins as __pi_studio_builtins",
|
|
11240
11276
|
"import traceback as __pi_studio_traceback",
|
|
11241
11277
|
`__pi_studio_code = ${JSON.stringify(code)}`,
|
|
11278
|
+
...prefix,
|
|
11242
11279
|
"try:",
|
|
11243
11280
|
" try:",
|
|
11244
11281
|
" __pi_studio_expr = compile(__pi_studio_code, '<pi-studio-repl>', 'eval')",
|
|
@@ -11251,12 +11288,15 @@ function buildStudioPythonControlSource(runtime: "python" | "ipython", code: str
|
|
|
11251
11288
|
"except Exception:",
|
|
11252
11289
|
" __pi_studio_traceback.print_exc()",
|
|
11253
11290
|
"finally:",
|
|
11291
|
+
...completion,
|
|
11254
11292
|
` __pi_studio_path(${JSON.stringify(doneFile)}).write_text('done\\n', encoding='utf-8')`,
|
|
11255
11293
|
].join("\n");
|
|
11256
11294
|
}
|
|
11257
11295
|
|
|
11258
|
-
function buildStudioJuliaControlSource(code: string, doneFile: string): string {
|
|
11296
|
+
function buildStudioJuliaControlSource(code: string, doneFile: string, display: ReplSubmissionDisplay): string {
|
|
11297
|
+
const completion = display.enabled ? [` Base.println(${JSON.stringify(display.endMarker)})`] : [];
|
|
11259
11298
|
return [
|
|
11299
|
+
...buildStudioJuliaDisplayStatements(display),
|
|
11260
11300
|
"try",
|
|
11261
11301
|
` local __pi_studio_result = Base.include_string(Main, ${JSON.stringify(code)}, "pi-studio-repl")`,
|
|
11262
11302
|
" if !isnothing(__pi_studio_result)",
|
|
@@ -11265,14 +11305,17 @@ function buildStudioJuliaControlSource(code: string, doneFile: string): string {
|
|
|
11265
11305
|
"catch e",
|
|
11266
11306
|
" Base.display_error(stderr, e, catch_backtrace())",
|
|
11267
11307
|
"finally",
|
|
11308
|
+
...completion,
|
|
11268
11309
|
` write(${JSON.stringify(doneFile)}, "done\\n")`,
|
|
11269
11310
|
"end",
|
|
11270
11311
|
].join("\n");
|
|
11271
11312
|
}
|
|
11272
11313
|
|
|
11273
|
-
function buildStudioRControlSource(code: string, doneFile: string): string {
|
|
11314
|
+
function buildStudioRControlSource(code: string, doneFile: string, display: ReplSubmissionDisplay): string {
|
|
11315
|
+
const completion = display.enabled ? [` base::cat(${JSON.stringify(`${display.endMarker}\n`)})`] : [];
|
|
11274
11316
|
return [
|
|
11275
11317
|
"local({",
|
|
11318
|
+
...buildStudioRDisplayStatements(display, " "),
|
|
11276
11319
|
` .__pi_studio_done_file <- ${JSON.stringify(doneFile)}`,
|
|
11277
11320
|
` .__pi_studio_code <- ${JSON.stringify(code)}`,
|
|
11278
11321
|
" tryCatch({",
|
|
@@ -11294,15 +11337,33 @@ function buildStudioRControlSource(code: string, doneFile: string): string {
|
|
|
11294
11337
|
" message(\"Error in \", .__pi_studio_call_text, \": \", conditionMessage(e))",
|
|
11295
11338
|
" }",
|
|
11296
11339
|
" }, finally = {",
|
|
11340
|
+
...completion,
|
|
11297
11341
|
" writeLines(\"done\", .__pi_studio_done_file)",
|
|
11298
11342
|
" })",
|
|
11299
11343
|
"})",
|
|
11300
11344
|
].join("\n");
|
|
11301
11345
|
}
|
|
11302
11346
|
|
|
11303
|
-
function
|
|
11347
|
+
function buildStudioGhciControlSource(code: string, display: ReplSubmissionDisplay): string {
|
|
11348
|
+
const prefix = display.enabled
|
|
11349
|
+
? display.prefixLines.map((line) => `:! command printf '%s\\n' ${shellQuote(line)}`)
|
|
11350
|
+
: [];
|
|
11351
|
+
return [
|
|
11352
|
+
...prefix,
|
|
11353
|
+
code.replace(/\r/g, "").trimEnd(),
|
|
11354
|
+
].filter(Boolean).join("\n") + "\n";
|
|
11355
|
+
}
|
|
11356
|
+
|
|
11357
|
+
function buildStudioGhciCompletionLine(doneFile: string, display: ReplSubmissionDisplay): string {
|
|
11358
|
+
const completion = display.enabled ? `command printf '%s\\n' ${shellQuote(display.endMarker)}; ` : "";
|
|
11359
|
+
return `:! ${completion}touch ${shellQuote(doneFile)}`;
|
|
11360
|
+
}
|
|
11361
|
+
|
|
11362
|
+
function buildStudioClojureControlSource(code: string, doneFile: string, display: ReplSubmissionDisplay): string {
|
|
11363
|
+
const completion = display.enabled ? [` (clojure.core/println ${JSON.stringify(display.endMarker)})`] : [];
|
|
11304
11364
|
return [
|
|
11305
11365
|
"(let [code " + JSON.stringify(code) + "]",
|
|
11366
|
+
...buildStudioClojureDisplayStatements(display, " "),
|
|
11306
11367
|
" (try",
|
|
11307
11368
|
" (let [rdr (clojure.lang.LineNumberingPushbackReader. (java.io.StringReader. code))]",
|
|
11308
11369
|
" (loop [last-val nil has-val false]",
|
|
@@ -11313,17 +11374,29 @@ function buildStudioClojureControlSource(code: string, doneFile: string): string
|
|
|
11313
11374
|
" (catch Throwable t",
|
|
11314
11375
|
" (#'clojure.main/repl-caught t))",
|
|
11315
11376
|
" (finally",
|
|
11377
|
+
...completion,
|
|
11316
11378
|
` (spit ${JSON.stringify(doneFile)} "done\\n"))))`,
|
|
11317
11379
|
].join("\n");
|
|
11318
11380
|
}
|
|
11319
11381
|
|
|
11320
|
-
function
|
|
11321
|
-
if (
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11382
|
+
function buildStudioShellControlSource(code: string, display: ReplSubmissionDisplay): string {
|
|
11383
|
+
if (!display.enabled) return `${code.replace(/\r/g, "").trimEnd()}\n`;
|
|
11384
|
+
return [
|
|
11385
|
+
...display.prefixLines.map((line) => `command printf '%s\\n' ${shellQuote(line)}`),
|
|
11386
|
+
code.replace(/\r/g, "").trimEnd(),
|
|
11387
|
+
"__pi_studio_repl_status=$?",
|
|
11388
|
+
`command printf '%s\\n' ${shellQuote(display.endMarker)}`,
|
|
11389
|
+
"return \"$__pi_studio_repl_status\"",
|
|
11390
|
+
].filter(Boolean).join("\n") + "\n";
|
|
11391
|
+
}
|
|
11392
|
+
|
|
11393
|
+
function buildStudioReplControlSource(runtime: StudioReplRuntime, code: string, doneFile: string, display: ReplSubmissionDisplay): string | null {
|
|
11394
|
+
if (runtime === "python" || runtime === "ipython") return buildStudioPythonControlSource(runtime, code, doneFile, display);
|
|
11395
|
+
if (runtime === "julia") return buildStudioJuliaControlSource(code, doneFile, display);
|
|
11396
|
+
if (runtime === "r") return buildStudioRControlSource(code, doneFile, display);
|
|
11397
|
+
if (runtime === "ghci") return buildStudioGhciControlSource(code, display);
|
|
11398
|
+
if (runtime === "clojure") return buildStudioClojureControlSource(code, doneFile, display);
|
|
11399
|
+
if (runtime === "shell") return buildStudioShellControlSource(code, display);
|
|
11327
11400
|
return null;
|
|
11328
11401
|
}
|
|
11329
11402
|
|
|
@@ -11340,29 +11413,36 @@ function buildStudioReplSubmissionLine(runtime: StudioReplRuntime, sourceFile: s
|
|
|
11340
11413
|
function prepareStudioReplSubmission(
|
|
11341
11414
|
sessionName: string,
|
|
11342
11415
|
source: string,
|
|
11416
|
+
details: { submissionId: string; echoMode: ReplSubmissionEchoMode },
|
|
11343
11417
|
runtimeHint?: StudioReplRuntime | "unknown",
|
|
11344
11418
|
): StudioReplPreparedSubmission {
|
|
11345
11419
|
const normalizedSource = String(source || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
11346
11420
|
const runtime = runtimeHint && runtimeHint !== "unknown" ? runtimeHint : inferStudioReplSessionRuntime(sessionName).runtime;
|
|
11347
11421
|
if (runtime !== "unknown") {
|
|
11348
|
-
const
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
}
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11422
|
+
const display = createReplSubmissionDisplay({
|
|
11423
|
+
entryId: details.submissionId,
|
|
11424
|
+
origin: "pi-studio",
|
|
11425
|
+
code: normalizedSource,
|
|
11426
|
+
mode: details.echoMode,
|
|
11427
|
+
});
|
|
11428
|
+
const controlFiles: StudioReplControlFiles = createPrivateReplControlFiles({
|
|
11429
|
+
extension: getStudioReplControlExtension(runtime),
|
|
11430
|
+
buildSource: ({ doneFile }: StudioReplControlFiles) => {
|
|
11431
|
+
const source = buildStudioReplControlSource(runtime, normalizedSource, doneFile, display);
|
|
11432
|
+
if (source === null) throw new Error(`No control-file wrapper is available for ${runtime}.`);
|
|
11433
|
+
return source;
|
|
11434
|
+
},
|
|
11435
|
+
});
|
|
11436
|
+
const submissionLine = buildStudioReplSubmissionLine(runtime, controlFiles.sourceFile, controlFiles.doneFile);
|
|
11437
|
+
const completionLine = runtime === "ghci" ? buildStudioGhciCompletionLine(controlFiles.doneFile, display) : undefined;
|
|
11438
|
+
return {
|
|
11439
|
+
runtime,
|
|
11440
|
+
usedControlFile: true,
|
|
11441
|
+
controlFiles,
|
|
11442
|
+
display,
|
|
11443
|
+
completionLine,
|
|
11444
|
+
submissionText: [submissionLine, completionLine].filter(Boolean).join("\n"),
|
|
11445
|
+
};
|
|
11366
11446
|
}
|
|
11367
11447
|
|
|
11368
11448
|
return {
|
|
@@ -11393,6 +11473,7 @@ function sendTextToStudioReplSession(
|
|
|
11393
11473
|
text: string,
|
|
11394
11474
|
paneTarget?: string,
|
|
11395
11475
|
runtimeHint?: StudioReplRuntime | "unknown",
|
|
11476
|
+
options: { submissionId?: string; echoMode?: string } = {},
|
|
11396
11477
|
): StudioReplSendSuccess | StudioReplSendFailure {
|
|
11397
11478
|
if (!/^[-_.A-Za-z0-9]+$/.test(sessionName)) return { ok: false, message: "Invalid REPL session name." };
|
|
11398
11479
|
const source = String(text || "");
|
|
@@ -11400,7 +11481,10 @@ function sendTextToStudioReplSession(
|
|
|
11400
11481
|
if (source.length > STUDIO_REPL_SEND_MAX_CHARS) {
|
|
11401
11482
|
return { ok: false, message: `REPL input is too large (${source.length} chars; max ${STUDIO_REPL_SEND_MAX_CHARS}).` };
|
|
11402
11483
|
}
|
|
11403
|
-
const prepared = prepareStudioReplSubmission(sessionName, source,
|
|
11484
|
+
const prepared = prepareStudioReplSubmission(sessionName, source, {
|
|
11485
|
+
submissionId: options.submissionId || `pi-studio:local:${randomUUID()}`,
|
|
11486
|
+
echoMode: normalizeReplSubmissionEchoMode(options.echoMode, STUDIO_REPL_TOOL_ECHO_MODE) as ReplSubmissionEchoMode,
|
|
11487
|
+
}, runtimeHint);
|
|
11404
11488
|
const pasted = pasteTextToStudioReplPane(sessionName, prepared.submissionText, paneTarget);
|
|
11405
11489
|
if (!pasted.ok) return {
|
|
11406
11490
|
ok: false,
|
|
@@ -11409,7 +11493,9 @@ function sendTextToStudioReplSession(
|
|
|
11409
11493
|
runtime: prepared.runtime,
|
|
11410
11494
|
usedControlFile: prepared.usedControlFile,
|
|
11411
11495
|
submissionText: prepared.submissionText,
|
|
11496
|
+
completionLine: prepared.completionLine,
|
|
11412
11497
|
controlFiles: prepared.controlFiles,
|
|
11498
|
+
display: prepared.display,
|
|
11413
11499
|
};
|
|
11414
11500
|
return {
|
|
11415
11501
|
ok: true,
|
|
@@ -11417,7 +11503,9 @@ function sendTextToStudioReplSession(
|
|
|
11417
11503
|
runtime: prepared.runtime,
|
|
11418
11504
|
usedControlFile: prepared.usedControlFile,
|
|
11419
11505
|
submissionText: prepared.submissionText,
|
|
11506
|
+
completionLine: prepared.completionLine,
|
|
11420
11507
|
controlFiles: prepared.controlFiles,
|
|
11508
|
+
display: prepared.display,
|
|
11421
11509
|
};
|
|
11422
11510
|
}
|
|
11423
11511
|
|
|
@@ -11442,15 +11530,16 @@ function stripStudioReplSubmissionEcho(output: string): string {
|
|
|
11442
11530
|
let value = String(output || "").replace(/^\s+/, "");
|
|
11443
11531
|
// The raw tmux mirror should stay raw, but Studio/tool result output should not
|
|
11444
11532
|
// expose the temp-file wrapper used to submit multiline snippets safely. The
|
|
11445
|
-
//
|
|
11446
|
-
//
|
|
11533
|
+
// root fragment catches both legacy long Studio paths and compact private
|
|
11534
|
+
// control paths. Match the Python wrapper by its final double close rather
|
|
11535
|
+
// than the word `globals`, which IPython may split across continuation lines.
|
|
11447
11536
|
const submissionEchoPatterns = [
|
|
11448
|
-
/^.*exec\(open\([\s\S]*?pi-studio-re[\s\S]
|
|
11449
|
-
/^.*include\([\s\S]*?pi-studio-re[\s\S]*?\.jl"\)\s*$/gm,
|
|
11450
|
-
/^.*source\([\s\S]*?pi-studio-re[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
11451
|
-
/^.*:script\s+[\s\S]*?pi-studio-re[\s\S]*?\.ghci"?\s*$/gm,
|
|
11452
|
-
/^.*\(do\s+\(load-file\s+[\s\S]*?pi-studio-re[\s\S]*?:pi-studio\/silent\)\s*$/gm,
|
|
11453
|
-
/^.*\.\s+[\s\S]*?pi-studio-re[\s\S]*?\.sh[\s\S]*?done\.flag.*$/gm,
|
|
11537
|
+
/^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\)\)\s*$/gm,
|
|
11538
|
+
/^.*include\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.jl"\)\s*$/gm,
|
|
11539
|
+
/^.*source\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
11540
|
+
/^.*:script\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.ghci"?\s*$/gm,
|
|
11541
|
+
/^.*\(do\s+\(load-file\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?:pi-studio\/silent\)\s*$/gm,
|
|
11542
|
+
/^.*\.\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.sh[\s\S]*?(?:done\.flag|[a-f0-9]{16}\.done).*$/gm,
|
|
11454
11543
|
];
|
|
11455
11544
|
for (const pattern of submissionEchoPatterns) value = value.replace(pattern, "");
|
|
11456
11545
|
return value.replace(/^(?:\s*\n)+/, "").replace(/[\t ]+$/gm, "").trimEnd();
|
|
@@ -11464,8 +11553,21 @@ function stripTrailingStudioReplPrompts(output: string): string {
|
|
|
11464
11553
|
return lines.join("\n").trimEnd();
|
|
11465
11554
|
}
|
|
11466
11555
|
|
|
11467
|
-
function
|
|
11468
|
-
|
|
11556
|
+
function stripStudioReplCompletionEcho(output: string, completionLine?: string): string {
|
|
11557
|
+
const value = String(output || "");
|
|
11558
|
+
if (!completionLine) return value;
|
|
11559
|
+
const completionIndex = value.lastIndexOf(completionLine);
|
|
11560
|
+
if (completionIndex < 0) return value;
|
|
11561
|
+
const lineStart = value.lastIndexOf("\n", completionIndex - 1) + 1;
|
|
11562
|
+
const newlineIndex = value.indexOf("\n", completionIndex + completionLine.length);
|
|
11563
|
+
const lineEnd = newlineIndex < 0 ? value.length : newlineIndex + 1;
|
|
11564
|
+
return value.slice(0, lineStart) + value.slice(lineEnd);
|
|
11565
|
+
}
|
|
11566
|
+
|
|
11567
|
+
function cleanStudioReplCapturedOutput(output: string, display?: ReplSubmissionDisplay, completionLine?: string): string {
|
|
11568
|
+
const completionCleaned = stripStudioReplCompletionEcho(output, completionLine);
|
|
11569
|
+
const displayCleaned = display ? stripReplSubmissionDisplay(completionCleaned, display) : completionCleaned;
|
|
11570
|
+
return stripTrailingStudioReplPrompts(stripStudioReplSubmissionEcho(displayCleaned));
|
|
11469
11571
|
}
|
|
11470
11572
|
|
|
11471
11573
|
function normalizeStudioReplJournalMode(mode: unknown): StudioReplJournalEntry["mode"] {
|
|
@@ -11554,7 +11656,10 @@ function isSameStudioReplSessionLifetime(left: StudioReplSessionInfo, right: Stu
|
|
|
11554
11656
|
&& left.tmuxSessionCreatedAt === right.tmuxSessionCreatedAt;
|
|
11555
11657
|
}
|
|
11556
11658
|
|
|
11557
|
-
function recordStudioReplJournalEntry(
|
|
11659
|
+
function recordStudioReplJournalEntry(
|
|
11660
|
+
details: Partial<StudioReplJournalEntry> & { sessionName: string; code?: string },
|
|
11661
|
+
knownSession?: StudioReplSessionInfo | null,
|
|
11662
|
+
): StudioReplJournalEntry {
|
|
11558
11663
|
let entry = upsertStudioReplJournalEntry(makeStudioReplJournalEntry({
|
|
11559
11664
|
...details,
|
|
11560
11665
|
origin: details.origin || "pi-studio",
|
|
@@ -11562,8 +11667,10 @@ function recordStudioReplJournalEntry(details: Partial<StudioReplJournalEntry> &
|
|
|
11562
11667
|
}));
|
|
11563
11668
|
const localEntryId = entry.id;
|
|
11564
11669
|
studioReplUnsyncedJournalEntryIds.add(localEntryId);
|
|
11565
|
-
|
|
11566
|
-
if
|
|
11670
|
+
// A previously validated exact-lifetime identity lets completion/error updates
|
|
11671
|
+
// reach the same sidecar even if that tmux session disappears before capture.
|
|
11672
|
+
const session = knownSession || inspectStudioReplSession(entry.sessionName);
|
|
11673
|
+
if (!session?.recordId || session.recordWarning || session.sessionName !== entry.sessionName) return entry;
|
|
11567
11674
|
try {
|
|
11568
11675
|
const recorded = upsertReplSessionRecordEntry(
|
|
11569
11676
|
session.recordId,
|
|
@@ -11619,7 +11726,13 @@ function getStudioReplJournalEntries(sessionName: string | null | undefined): St
|
|
|
11619
11726
|
}));
|
|
11620
11727
|
}
|
|
11621
11728
|
|
|
11622
|
-
function updateStudioReplJournalEntryOutput(
|
|
11729
|
+
function updateStudioReplJournalEntryOutput(
|
|
11730
|
+
requestId: string,
|
|
11731
|
+
sessionName: string,
|
|
11732
|
+
output: string,
|
|
11733
|
+
status: StudioReplJournalEntry["status"],
|
|
11734
|
+
knownSession?: StudioReplSessionInfo | null,
|
|
11735
|
+
): void {
|
|
11623
11736
|
const normalizedRequestId = String(requestId || "");
|
|
11624
11737
|
const normalizedSessionName = String(sessionName || "");
|
|
11625
11738
|
const existing = getStudioReplJournalEntries(normalizedSessionName).slice().reverse().find((entry) => (
|
|
@@ -11633,7 +11746,7 @@ function updateStudioReplJournalEntryOutput(requestId: string, sessionName: stri
|
|
|
11633
11746
|
status,
|
|
11634
11747
|
completedAt: Date.now(),
|
|
11635
11748
|
updatedAt: Date.now(),
|
|
11636
|
-
});
|
|
11749
|
+
}, knownSession);
|
|
11637
11750
|
}
|
|
11638
11751
|
|
|
11639
11752
|
function clearStudioReplJournal(sessionName: string): void {
|
|
@@ -11665,19 +11778,20 @@ function sleepWithoutKeepingStudioProcessAlive(ms: number): Promise<void> {
|
|
|
11665
11778
|
});
|
|
11666
11779
|
}
|
|
11667
11780
|
|
|
11668
|
-
function
|
|
11781
|
+
function retainStudioReplSubmissionUntilSettled(
|
|
11669
11782
|
session: StudioReplSessionInfo,
|
|
11670
|
-
|
|
11671
|
-
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease
|
|
11783
|
+
controlFiles: StudioReplControlFiles,
|
|
11784
|
+
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null,
|
|
11672
11785
|
): void {
|
|
11673
11786
|
// A caller timeout or abort does not stop code already submitted to tmux.
|
|
11674
|
-
//
|
|
11675
|
-
// completion, or until this exact tmux
|
|
11787
|
+
// Keep the private control files (and any shared lease) until the runtime
|
|
11788
|
+
// wrapper reports completion, or until this exact tmux lifetime disappears.
|
|
11676
11789
|
void (async () => {
|
|
11677
11790
|
let nextIdentityCheck = 0;
|
|
11678
11791
|
let missingChecks = 0;
|
|
11679
11792
|
try {
|
|
11680
|
-
while (!existsSync(doneFile)) {
|
|
11793
|
+
while (!existsSync(controlFiles.doneFile)) {
|
|
11794
|
+
if (!existsSync(controlFiles.sourceFile)) return;
|
|
11681
11795
|
if (Date.now() >= nextIdentityCheck) {
|
|
11682
11796
|
try {
|
|
11683
11797
|
const current = inspectStudioReplSession(session.sessionName);
|
|
@@ -11696,21 +11810,23 @@ function retainStudioReplSendLeaseUntilSubmissionSettles(
|
|
|
11696
11810
|
await sleepWithoutKeepingStudioProcessAlive(100);
|
|
11697
11811
|
}
|
|
11698
11812
|
} finally {
|
|
11699
|
-
|
|
11813
|
+
cleanupPrivateReplControlFiles(controlFiles);
|
|
11814
|
+
await lease?.release().catch(() => undefined);
|
|
11700
11815
|
}
|
|
11701
11816
|
})();
|
|
11702
11817
|
}
|
|
11703
11818
|
|
|
11704
|
-
async function
|
|
11705
|
-
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease
|
|
11819
|
+
async function releaseOrRetainStudioReplSubmission(
|
|
11820
|
+
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null,
|
|
11706
11821
|
session: StudioReplSessionInfo | null,
|
|
11707
|
-
|
|
11822
|
+
controlFiles: StudioReplControlFiles | undefined,
|
|
11708
11823
|
): Promise<void> {
|
|
11709
|
-
if (session &&
|
|
11710
|
-
|
|
11824
|
+
if (session && controlFiles && !existsSync(controlFiles.doneFile)) {
|
|
11825
|
+
retainStudioReplSubmissionUntilSettled(session, controlFiles, lease);
|
|
11711
11826
|
return;
|
|
11712
11827
|
}
|
|
11713
|
-
|
|
11828
|
+
cleanupPrivateReplControlFiles(controlFiles);
|
|
11829
|
+
await lease?.release().catch(() => undefined);
|
|
11714
11830
|
}
|
|
11715
11831
|
|
|
11716
11832
|
function interruptStudioReplSession(sessionName: string): { ok: true; message: string } | { ok: false; message: string } {
|
|
@@ -12319,6 +12435,11 @@ ${cssVarsBlock}
|
|
|
12319
12435
|
<option value="raw" selected>Send mode: Raw</option>
|
|
12320
12436
|
<option value="literate">Send mode: Literate</option>
|
|
12321
12437
|
</select>
|
|
12438
|
+
<select id="replEchoModeSelect" class="studio-flat-select" hidden aria-label="REPL submission echo" title="Choose how much submitted code Studio displays in the raw REPL pane.">
|
|
12439
|
+
<option value="off" selected>Pane echo: Off</option>
|
|
12440
|
+
<option value="summary">Pane echo: Summary</option>
|
|
12441
|
+
<option value="full">Pane echo: Full (raw code)</option>
|
|
12442
|
+
</select>
|
|
12322
12443
|
</div>
|
|
12323
12444
|
<div class="source-actions-row">
|
|
12324
12445
|
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy</button>
|
|
@@ -12388,6 +12509,15 @@ ${cssVarsBlock}
|
|
|
12388
12509
|
<option value="off">Line numbers: Off</option>
|
|
12389
12510
|
<option value="on" selected>Line numbers: On</option>
|
|
12390
12511
|
</select>
|
|
12512
|
+
<select id="studioPaneLayoutSelect" class="studio-flat-select" aria-label="Studio pane layout" title="Choose whether the two main panes sit side by side or in a vertical stack.">
|
|
12513
|
+
<option value="side-by-side">Layout: Side by side</option>
|
|
12514
|
+
<option value="editor-top">Layout: Editor above</option>
|
|
12515
|
+
<option value="response-top">Layout: Response above</option>
|
|
12516
|
+
</select>
|
|
12517
|
+
<select id="activityTrackingSelect" class="studio-flat-select" aria-label="Follow main Pi activity" title="Optionally follow main Pi activity from Working to the response view; off by default.">
|
|
12518
|
+
<option value="off">Follow activity: Off</option>
|
|
12519
|
+
<option value="on">Follow activity: On</option>
|
|
12520
|
+
</select>
|
|
12391
12521
|
<select id="editorFontSizeSelect" class="studio-flat-select" aria-label="Editor text size" title="Adjust raw editor text size.">
|
|
12392
12522
|
<option value="10">Editor text: 10px</option>
|
|
12393
12523
|
<option value="11">Editor text: 11px</option>
|
|
@@ -12487,7 +12617,7 @@ ${cssVarsBlock}
|
|
|
12487
12617
|
<div id="rightSectionHeader" class="section-header">
|
|
12488
12618
|
<div class="section-header-main">
|
|
12489
12619
|
<span id="rightViewSelectWrap" class="studio-header-select-wrap">
|
|
12490
|
-
<select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+1–8 switches directly between all right-pane views. Cmd/Ctrl+Alt+P/E/W/F/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, and Side questions.">
|
|
12620
|
+
<select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+1–8 switches directly between all right-pane views. Cmd/Ctrl+Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.">
|
|
12491
12621
|
<option value="markdown">Response (Raw)</option>
|
|
12492
12622
|
<option value="preview" selected>Response (Preview)</option>
|
|
12493
12623
|
<option value="editor-preview">Editor (Preview)</option>
|
|
@@ -12605,6 +12735,7 @@ ${cssVarsBlock}
|
|
|
12605
12735
|
<div><dt>Cmd/Ctrl+Alt+E</dt><dd>Switch the right pane directly to Editor Preview</dd></div>
|
|
12606
12736
|
<div><dt>Cmd/Ctrl+Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
|
|
12607
12737
|
<div><dt>Cmd/Ctrl+Alt+F</dt><dd>Switch the right pane directly to Files</dd></div>
|
|
12738
|
+
<div><dt>Cmd/Ctrl+Alt+R</dt><dd>Switch the right pane directly to REPL and focus Quick send when a session is selected</dd></div>
|
|
12608
12739
|
<div><dt>Cmd/Ctrl+Alt+Q</dt><dd>Switch the right pane directly to Side questions</dd></div>
|
|
12609
12740
|
<div><dt>F8</dt><dd>Focus editor text</dd></div>
|
|
12610
12741
|
<div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
|
|
@@ -12620,7 +12751,7 @@ ${cssVarsBlock}
|
|
|
12620
12751
|
<div><dt>Alt/Option+=</dt><dd>Increase the active pane's text size when not editing text</dd></div>
|
|
12621
12752
|
<div><dt>Alt/Option+-</dt><dd>Decrease the active pane's text size when not editing text</dd></div>
|
|
12622
12753
|
<div><dt>Alt/Option+0</dt><dd>Reset the active pane's text size when not editing text</dd></div>
|
|
12623
|
-
<div><dt>Cmd/Ctrl+Alt+R</dt><dd>Refresh the focused or visible PDF preview from disk</dd></div>
|
|
12754
|
+
<div><dt>Cmd/Ctrl+Alt+Shift+R</dt><dd>Refresh the focused or visible PDF preview from disk</dd></div>
|
|
12624
12755
|
</dl>
|
|
12625
12756
|
</section>
|
|
12626
12757
|
<section class="shortcuts-group">
|
|
@@ -12652,6 +12783,7 @@ ${cssVarsBlock}
|
|
|
12652
12783
|
<section class="shortcuts-group">
|
|
12653
12784
|
<h3>REPL</h3>
|
|
12654
12785
|
<dl>
|
|
12786
|
+
<div><dt>Cmd/Ctrl+Enter</dt><dd>Send exact Quick send text while its REPL composer is focused; Enter adds a new line</dd></div>
|
|
12655
12787
|
<div><dt>Cmd/Ctrl+Shift+Enter</dt><dd>Send selection, chunks, or editor text to the active REPL when the right pane is REPL</dd></div>
|
|
12656
12788
|
</dl>
|
|
12657
12789
|
</section>
|
|
@@ -12889,6 +13021,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12889
13021
|
promptGuidelines: [
|
|
12890
13022
|
"Use studio_repl_send when the user asks to run code in the active Studio REPL.",
|
|
12891
13023
|
"Do not improvise tmux paste-buffer commands for Studio REPL code; studio_repl_send handles multiline quoting and runtime-specific submission.",
|
|
13024
|
+
"Submitted-code display is off by default; use echoMode='summary' or echoMode='full' only when the user asks to show code and alignment anchors in the raw pane.",
|
|
12892
13025
|
"If several REPL sessions of the same runtime are running, use studio_repl_status first or pass the exact sessionName when known.",
|
|
12893
13026
|
],
|
|
12894
13027
|
parameters: STUDIO_REPL_SEND_TOOL_PARAMS,
|
|
@@ -12905,7 +13038,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12905
13038
|
const timeoutMs = clampStudioReplSendTimeout(params.timeoutMs);
|
|
12906
13039
|
let lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null = null;
|
|
12907
13040
|
let submittedSession: StudioReplSessionInfo | null = null;
|
|
12908
|
-
let
|
|
13041
|
+
let submittedControlFiles: StudioReplControlFiles | undefined;
|
|
12909
13042
|
let journalEntry: StudioReplJournalEntry | null = null;
|
|
12910
13043
|
try {
|
|
12911
13044
|
if (selected.session.recordId && !selected.session.recordWarning) {
|
|
@@ -12938,26 +13071,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
12938
13071
|
mode: "agent",
|
|
12939
13072
|
code: params.code,
|
|
12940
13073
|
status: "sending",
|
|
13074
|
+
}, currentSession);
|
|
13075
|
+
const sent = sendTextToStudioReplSession(currentSession.sessionName, params.code, currentSession.target, currentSession.runtime, {
|
|
13076
|
+
submissionId: journalEntry.id,
|
|
13077
|
+
echoMode: params.echoMode || STUDIO_REPL_TOOL_ECHO_MODE,
|
|
12941
13078
|
});
|
|
12942
|
-
|
|
13079
|
+
submittedControlFiles = sent.controlFiles;
|
|
12943
13080
|
if (!sent.ok) {
|
|
12944
|
-
if (sent.submissionStarted)
|
|
12945
|
-
submittedSession = currentSession;
|
|
12946
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
12947
|
-
}
|
|
13081
|
+
if (sent.submissionStarted) submittedSession = currentSession;
|
|
12948
13082
|
journalEntry = recordStudioReplJournalEntry({
|
|
12949
13083
|
...journalEntry,
|
|
12950
13084
|
output: sent.message,
|
|
12951
13085
|
status: "error",
|
|
12952
13086
|
completedAt: Date.now(),
|
|
12953
|
-
});
|
|
13087
|
+
}, currentSession);
|
|
12954
13088
|
return {
|
|
12955
13089
|
content: [{ type: "text", text: sent.message }],
|
|
12956
13090
|
details: { ok: false, error: sent.message, session: selected.session, sessions: selected.sessions, recordEntryId: journalEntry.id } as Record<string, unknown>,
|
|
12957
13091
|
};
|
|
12958
13092
|
}
|
|
12959
13093
|
submittedSession = currentSession;
|
|
12960
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
12961
13094
|
studioReplActiveSessionName = selected.session.sessionName;
|
|
12962
13095
|
|
|
12963
13096
|
let completed = false;
|
|
@@ -12976,7 +13109,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12976
13109
|
}
|
|
12977
13110
|
const afterTranscript = after.transcript;
|
|
12978
13111
|
const rawOutput = extractStudioReplTranscriptDelta(beforeTranscript, afterTranscript);
|
|
12979
|
-
const output = cleanStudioReplCapturedOutput(rawOutput);
|
|
13112
|
+
const output = cleanStudioReplCapturedOutput(rawOutput, sent.display, sent.completionLine);
|
|
12980
13113
|
const status: StudioReplJournalEntry["status"] = sent.controlFiles?.doneFile
|
|
12981
13114
|
? (completed ? "captured" : "timeout")
|
|
12982
13115
|
: (output.trim() ? "captured" : "sent");
|
|
@@ -12986,7 +13119,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12986
13119
|
output,
|
|
12987
13120
|
status,
|
|
12988
13121
|
completedAt: Date.now(),
|
|
12989
|
-
});
|
|
13122
|
+
}, currentSession);
|
|
12990
13123
|
const statusLine = sent.controlFiles?.doneFile
|
|
12991
13124
|
? (completed ? "Completed." : `Timed out after ${timeoutMs} ms waiting for completion marker.`)
|
|
12992
13125
|
: "Submitted.";
|
|
@@ -13022,6 +13155,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
13022
13155
|
usedControlFile: sent.usedControlFile,
|
|
13023
13156
|
submissionText: sent.submissionText,
|
|
13024
13157
|
controlFiles: sent.controlFiles,
|
|
13158
|
+
echoMode: sent.display?.mode || "off",
|
|
13159
|
+
submissionAnchorId: sent.display?.enabled ? sent.display.anchorId : undefined,
|
|
13025
13160
|
output,
|
|
13026
13161
|
recordEntryId: journalEntry.id,
|
|
13027
13162
|
recordPath: selected.session.recordPath,
|
|
@@ -13035,17 +13170,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
13035
13170
|
output: error instanceof Error ? error.message : String(error),
|
|
13036
13171
|
status: error instanceof Error && /timed out/i.test(error.message) ? "timeout" : "error",
|
|
13037
13172
|
completedAt: Date.now(),
|
|
13038
|
-
});
|
|
13173
|
+
}, submittedSession || selected.session);
|
|
13039
13174
|
} catch {
|
|
13040
13175
|
// Preserve the execution error when record maintenance also fails.
|
|
13041
13176
|
}
|
|
13042
13177
|
}
|
|
13043
13178
|
throw error;
|
|
13044
13179
|
} finally {
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
}
|
|
13180
|
+
await releaseOrRetainStudioReplSubmission(lease, submittedSession, submittedControlFiles);
|
|
13181
|
+
lease = null;
|
|
13182
|
+
submittedControlFiles = undefined;
|
|
13049
13183
|
}
|
|
13050
13184
|
},
|
|
13051
13185
|
});
|
|
@@ -15914,7 +16048,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
15914
16048
|
let journalEntry: StudioReplJournalEntry | null = null;
|
|
15915
16049
|
let lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null = null;
|
|
15916
16050
|
let submittedSession: StudioReplSessionInfo | null = null;
|
|
15917
|
-
let
|
|
16051
|
+
let submittedControlFiles: StudioReplControlFiles | undefined;
|
|
15918
16052
|
try {
|
|
15919
16053
|
if (!session) throw new Error(`No tmux REPL session named ${msg.sessionName}.`);
|
|
15920
16054
|
if (session.recordId && !session.recordWarning) {
|
|
@@ -15949,17 +16083,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
15949
16083
|
code: msg.text,
|
|
15950
16084
|
status: "sending",
|
|
15951
16085
|
skippedChunks: msg.skippedChunks,
|
|
16086
|
+
}, currentSession);
|
|
16087
|
+
const sent = sendTextToStudioReplSession(msg.sessionName, msg.text, currentSession.target, currentSession.runtime, {
|
|
16088
|
+
submissionId: journalEntry.id,
|
|
16089
|
+
echoMode: msg.echoMode,
|
|
15952
16090
|
});
|
|
15953
|
-
|
|
16091
|
+
submittedControlFiles = sent.controlFiles;
|
|
15954
16092
|
if (!sent.ok) {
|
|
15955
|
-
if (sent.submissionStarted)
|
|
15956
|
-
submittedSession = currentSession;
|
|
15957
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
15958
|
-
}
|
|
16093
|
+
if (sent.submissionStarted) submittedSession = currentSession;
|
|
15959
16094
|
throw new Error(sent.message);
|
|
15960
16095
|
}
|
|
15961
16096
|
submittedSession = currentSession;
|
|
15962
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
15963
16097
|
studioReplActiveSessionName = msg.sessionName;
|
|
15964
16098
|
sendToClient(client, {
|
|
15965
16099
|
type: "repl_send_ack",
|
|
@@ -15982,29 +16116,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
15982
16116
|
}
|
|
15983
16117
|
const afterTranscript = after.transcript;
|
|
15984
16118
|
const rawOutput = extractStudioReplTranscriptDelta(beforeTranscript, afterTranscript);
|
|
15985
|
-
const output = cleanStudioReplCapturedOutput(rawOutput);
|
|
16119
|
+
const output = cleanStudioReplCapturedOutput(rawOutput, sent.display, sent.completionLine);
|
|
15986
16120
|
updateStudioReplJournalEntryOutput(
|
|
15987
16121
|
msg.requestId,
|
|
15988
16122
|
msg.sessionName,
|
|
15989
16123
|
output,
|
|
15990
16124
|
sent.controlFiles?.doneFile ? (completed ? "captured" : "timeout") : (output.trim() ? "captured" : "sent"),
|
|
16125
|
+
currentSession,
|
|
15991
16126
|
);
|
|
15992
|
-
if (lease) {
|
|
15993
|
-
await releaseOrRetainStudioReplSendLease(lease, submittedSession, submittedDoneFile);
|
|
15994
|
-
lease = null;
|
|
15995
|
-
}
|
|
15996
16127
|
sendReplCaptureToClient(client, msg.sessionName, { requestId: msg.requestId });
|
|
15997
16128
|
} catch (error) {
|
|
15998
16129
|
const message = error instanceof Error ? error.message : String(error);
|
|
15999
|
-
if (journalEntry) updateStudioReplJournalEntryOutput(
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16130
|
+
if (journalEntry) updateStudioReplJournalEntryOutput(
|
|
16131
|
+
msg.requestId,
|
|
16132
|
+
msg.sessionName,
|
|
16133
|
+
message,
|
|
16134
|
+
"error",
|
|
16135
|
+
submittedSession || session,
|
|
16136
|
+
);
|
|
16004
16137
|
sendToClient(client, { type: "error", requestId: msg.requestId, message });
|
|
16005
16138
|
sendReplCaptureToClient(client, msg.sessionName, { requestId: msg.requestId, replError: message });
|
|
16006
16139
|
} finally {
|
|
16007
|
-
await lease
|
|
16140
|
+
await releaseOrRetainStudioReplSubmission(lease, submittedSession, submittedControlFiles);
|
|
16141
|
+
lease = null;
|
|
16142
|
+
submittedControlFiles = undefined;
|
|
16008
16143
|
}
|
|
16009
16144
|
})();
|
|
16010
16145
|
return;
|