pi-studio 0.9.56 → 0.9.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +17 -3
- package/ROADMAP.md +16 -1
- package/client/studio-client.js +65 -7
- package/index.ts +235 -112
- 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,15 @@ 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
|
+
// The root fragment catches both legacy long Studio paths and compact private
|
|
11534
|
+
// control paths when IPython wraps them across continuation prompt lines.
|
|
11447
11535
|
const submissionEchoPatterns = [
|
|
11448
|
-
/^.*exec\(open\([\s\S]*?pi-studio-re[\s\S]*?globals\(\)\)\s*$/gm,
|
|
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,
|
|
11536
|
+
/^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?globals\(\)\)\s*$/gm,
|
|
11537
|
+
/^.*include\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.jl"\)\s*$/gm,
|
|
11538
|
+
/^.*source\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
11539
|
+
/^.*:script\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.ghci"?\s*$/gm,
|
|
11540
|
+
/^.*\(do\s+\(load-file\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?:pi-studio\/silent\)\s*$/gm,
|
|
11541
|
+
/^.*\.\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.sh[\s\S]*?(?:done\.flag|[a-f0-9]{16}\.done).*$/gm,
|
|
11454
11542
|
];
|
|
11455
11543
|
for (const pattern of submissionEchoPatterns) value = value.replace(pattern, "");
|
|
11456
11544
|
return value.replace(/^(?:\s*\n)+/, "").replace(/[\t ]+$/gm, "").trimEnd();
|
|
@@ -11464,8 +11552,21 @@ function stripTrailingStudioReplPrompts(output: string): string {
|
|
|
11464
11552
|
return lines.join("\n").trimEnd();
|
|
11465
11553
|
}
|
|
11466
11554
|
|
|
11467
|
-
function
|
|
11468
|
-
|
|
11555
|
+
function stripStudioReplCompletionEcho(output: string, completionLine?: string): string {
|
|
11556
|
+
const value = String(output || "");
|
|
11557
|
+
if (!completionLine) return value;
|
|
11558
|
+
const completionIndex = value.lastIndexOf(completionLine);
|
|
11559
|
+
if (completionIndex < 0) return value;
|
|
11560
|
+
const lineStart = value.lastIndexOf("\n", completionIndex - 1) + 1;
|
|
11561
|
+
const newlineIndex = value.indexOf("\n", completionIndex + completionLine.length);
|
|
11562
|
+
const lineEnd = newlineIndex < 0 ? value.length : newlineIndex + 1;
|
|
11563
|
+
return value.slice(0, lineStart) + value.slice(lineEnd);
|
|
11564
|
+
}
|
|
11565
|
+
|
|
11566
|
+
function cleanStudioReplCapturedOutput(output: string, display?: ReplSubmissionDisplay, completionLine?: string): string {
|
|
11567
|
+
const completionCleaned = stripStudioReplCompletionEcho(output, completionLine);
|
|
11568
|
+
const displayCleaned = display ? stripReplSubmissionDisplay(completionCleaned, display) : completionCleaned;
|
|
11569
|
+
return stripTrailingStudioReplPrompts(stripStudioReplSubmissionEcho(displayCleaned));
|
|
11469
11570
|
}
|
|
11470
11571
|
|
|
11471
11572
|
function normalizeStudioReplJournalMode(mode: unknown): StudioReplJournalEntry["mode"] {
|
|
@@ -11554,7 +11655,10 @@ function isSameStudioReplSessionLifetime(left: StudioReplSessionInfo, right: Stu
|
|
|
11554
11655
|
&& left.tmuxSessionCreatedAt === right.tmuxSessionCreatedAt;
|
|
11555
11656
|
}
|
|
11556
11657
|
|
|
11557
|
-
function recordStudioReplJournalEntry(
|
|
11658
|
+
function recordStudioReplJournalEntry(
|
|
11659
|
+
details: Partial<StudioReplJournalEntry> & { sessionName: string; code?: string },
|
|
11660
|
+
knownSession?: StudioReplSessionInfo | null,
|
|
11661
|
+
): StudioReplJournalEntry {
|
|
11558
11662
|
let entry = upsertStudioReplJournalEntry(makeStudioReplJournalEntry({
|
|
11559
11663
|
...details,
|
|
11560
11664
|
origin: details.origin || "pi-studio",
|
|
@@ -11562,8 +11666,10 @@ function recordStudioReplJournalEntry(details: Partial<StudioReplJournalEntry> &
|
|
|
11562
11666
|
}));
|
|
11563
11667
|
const localEntryId = entry.id;
|
|
11564
11668
|
studioReplUnsyncedJournalEntryIds.add(localEntryId);
|
|
11565
|
-
|
|
11566
|
-
if
|
|
11669
|
+
// A previously validated exact-lifetime identity lets completion/error updates
|
|
11670
|
+
// reach the same sidecar even if that tmux session disappears before capture.
|
|
11671
|
+
const session = knownSession || inspectStudioReplSession(entry.sessionName);
|
|
11672
|
+
if (!session?.recordId || session.recordWarning || session.sessionName !== entry.sessionName) return entry;
|
|
11567
11673
|
try {
|
|
11568
11674
|
const recorded = upsertReplSessionRecordEntry(
|
|
11569
11675
|
session.recordId,
|
|
@@ -11619,7 +11725,13 @@ function getStudioReplJournalEntries(sessionName: string | null | undefined): St
|
|
|
11619
11725
|
}));
|
|
11620
11726
|
}
|
|
11621
11727
|
|
|
11622
|
-
function updateStudioReplJournalEntryOutput(
|
|
11728
|
+
function updateStudioReplJournalEntryOutput(
|
|
11729
|
+
requestId: string,
|
|
11730
|
+
sessionName: string,
|
|
11731
|
+
output: string,
|
|
11732
|
+
status: StudioReplJournalEntry["status"],
|
|
11733
|
+
knownSession?: StudioReplSessionInfo | null,
|
|
11734
|
+
): void {
|
|
11623
11735
|
const normalizedRequestId = String(requestId || "");
|
|
11624
11736
|
const normalizedSessionName = String(sessionName || "");
|
|
11625
11737
|
const existing = getStudioReplJournalEntries(normalizedSessionName).slice().reverse().find((entry) => (
|
|
@@ -11633,7 +11745,7 @@ function updateStudioReplJournalEntryOutput(requestId: string, sessionName: stri
|
|
|
11633
11745
|
status,
|
|
11634
11746
|
completedAt: Date.now(),
|
|
11635
11747
|
updatedAt: Date.now(),
|
|
11636
|
-
});
|
|
11748
|
+
}, knownSession);
|
|
11637
11749
|
}
|
|
11638
11750
|
|
|
11639
11751
|
function clearStudioReplJournal(sessionName: string): void {
|
|
@@ -11665,19 +11777,20 @@ function sleepWithoutKeepingStudioProcessAlive(ms: number): Promise<void> {
|
|
|
11665
11777
|
});
|
|
11666
11778
|
}
|
|
11667
11779
|
|
|
11668
|
-
function
|
|
11780
|
+
function retainStudioReplSubmissionUntilSettled(
|
|
11669
11781
|
session: StudioReplSessionInfo,
|
|
11670
|
-
|
|
11671
|
-
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease
|
|
11782
|
+
controlFiles: StudioReplControlFiles,
|
|
11783
|
+
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null,
|
|
11672
11784
|
): void {
|
|
11673
11785
|
// A caller timeout or abort does not stop code already submitted to tmux.
|
|
11674
|
-
//
|
|
11675
|
-
// completion, or until this exact tmux
|
|
11786
|
+
// Keep the private control files (and any shared lease) until the runtime
|
|
11787
|
+
// wrapper reports completion, or until this exact tmux lifetime disappears.
|
|
11676
11788
|
void (async () => {
|
|
11677
11789
|
let nextIdentityCheck = 0;
|
|
11678
11790
|
let missingChecks = 0;
|
|
11679
11791
|
try {
|
|
11680
|
-
while (!existsSync(doneFile)) {
|
|
11792
|
+
while (!existsSync(controlFiles.doneFile)) {
|
|
11793
|
+
if (!existsSync(controlFiles.sourceFile)) return;
|
|
11681
11794
|
if (Date.now() >= nextIdentityCheck) {
|
|
11682
11795
|
try {
|
|
11683
11796
|
const current = inspectStudioReplSession(session.sessionName);
|
|
@@ -11696,21 +11809,23 @@ function retainStudioReplSendLeaseUntilSubmissionSettles(
|
|
|
11696
11809
|
await sleepWithoutKeepingStudioProcessAlive(100);
|
|
11697
11810
|
}
|
|
11698
11811
|
} finally {
|
|
11699
|
-
|
|
11812
|
+
cleanupPrivateReplControlFiles(controlFiles);
|
|
11813
|
+
await lease?.release().catch(() => undefined);
|
|
11700
11814
|
}
|
|
11701
11815
|
})();
|
|
11702
11816
|
}
|
|
11703
11817
|
|
|
11704
|
-
async function
|
|
11705
|
-
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease
|
|
11818
|
+
async function releaseOrRetainStudioReplSubmission(
|
|
11819
|
+
lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null,
|
|
11706
11820
|
session: StudioReplSessionInfo | null,
|
|
11707
|
-
|
|
11821
|
+
controlFiles: StudioReplControlFiles | undefined,
|
|
11708
11822
|
): Promise<void> {
|
|
11709
|
-
if (session &&
|
|
11710
|
-
|
|
11823
|
+
if (session && controlFiles && !existsSync(controlFiles.doneFile)) {
|
|
11824
|
+
retainStudioReplSubmissionUntilSettled(session, controlFiles, lease);
|
|
11711
11825
|
return;
|
|
11712
11826
|
}
|
|
11713
|
-
|
|
11827
|
+
cleanupPrivateReplControlFiles(controlFiles);
|
|
11828
|
+
await lease?.release().catch(() => undefined);
|
|
11714
11829
|
}
|
|
11715
11830
|
|
|
11716
11831
|
function interruptStudioReplSession(sessionName: string): { ok: true; message: string } | { ok: false; message: string } {
|
|
@@ -12319,6 +12434,11 @@ ${cssVarsBlock}
|
|
|
12319
12434
|
<option value="raw" selected>Send mode: Raw</option>
|
|
12320
12435
|
<option value="literate">Send mode: Literate</option>
|
|
12321
12436
|
</select>
|
|
12437
|
+
<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.">
|
|
12438
|
+
<option value="off" selected>Pane echo: Off</option>
|
|
12439
|
+
<option value="summary">Pane echo: Summary</option>
|
|
12440
|
+
<option value="full">Pane echo: Full (raw code)</option>
|
|
12441
|
+
</select>
|
|
12322
12442
|
</div>
|
|
12323
12443
|
<div class="source-actions-row">
|
|
12324
12444
|
<button id="copyDraftBtn" type="button" title="Copy the current editor text to the clipboard.">Copy</button>
|
|
@@ -12889,6 +13009,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12889
13009
|
promptGuidelines: [
|
|
12890
13010
|
"Use studio_repl_send when the user asks to run code in the active Studio REPL.",
|
|
12891
13011
|
"Do not improvise tmux paste-buffer commands for Studio REPL code; studio_repl_send handles multiline quoting and runtime-specific submission.",
|
|
13012
|
+
"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
13013
|
"If several REPL sessions of the same runtime are running, use studio_repl_status first or pass the exact sessionName when known.",
|
|
12893
13014
|
],
|
|
12894
13015
|
parameters: STUDIO_REPL_SEND_TOOL_PARAMS,
|
|
@@ -12905,7 +13026,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12905
13026
|
const timeoutMs = clampStudioReplSendTimeout(params.timeoutMs);
|
|
12906
13027
|
let lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null = null;
|
|
12907
13028
|
let submittedSession: StudioReplSessionInfo | null = null;
|
|
12908
|
-
let
|
|
13029
|
+
let submittedControlFiles: StudioReplControlFiles | undefined;
|
|
12909
13030
|
let journalEntry: StudioReplJournalEntry | null = null;
|
|
12910
13031
|
try {
|
|
12911
13032
|
if (selected.session.recordId && !selected.session.recordWarning) {
|
|
@@ -12938,26 +13059,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
12938
13059
|
mode: "agent",
|
|
12939
13060
|
code: params.code,
|
|
12940
13061
|
status: "sending",
|
|
13062
|
+
}, currentSession);
|
|
13063
|
+
const sent = sendTextToStudioReplSession(currentSession.sessionName, params.code, currentSession.target, currentSession.runtime, {
|
|
13064
|
+
submissionId: journalEntry.id,
|
|
13065
|
+
echoMode: params.echoMode || STUDIO_REPL_TOOL_ECHO_MODE,
|
|
12941
13066
|
});
|
|
12942
|
-
|
|
13067
|
+
submittedControlFiles = sent.controlFiles;
|
|
12943
13068
|
if (!sent.ok) {
|
|
12944
|
-
if (sent.submissionStarted)
|
|
12945
|
-
submittedSession = currentSession;
|
|
12946
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
12947
|
-
}
|
|
13069
|
+
if (sent.submissionStarted) submittedSession = currentSession;
|
|
12948
13070
|
journalEntry = recordStudioReplJournalEntry({
|
|
12949
13071
|
...journalEntry,
|
|
12950
13072
|
output: sent.message,
|
|
12951
13073
|
status: "error",
|
|
12952
13074
|
completedAt: Date.now(),
|
|
12953
|
-
});
|
|
13075
|
+
}, currentSession);
|
|
12954
13076
|
return {
|
|
12955
13077
|
content: [{ type: "text", text: sent.message }],
|
|
12956
13078
|
details: { ok: false, error: sent.message, session: selected.session, sessions: selected.sessions, recordEntryId: journalEntry.id } as Record<string, unknown>,
|
|
12957
13079
|
};
|
|
12958
13080
|
}
|
|
12959
13081
|
submittedSession = currentSession;
|
|
12960
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
12961
13082
|
studioReplActiveSessionName = selected.session.sessionName;
|
|
12962
13083
|
|
|
12963
13084
|
let completed = false;
|
|
@@ -12976,7 +13097,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12976
13097
|
}
|
|
12977
13098
|
const afterTranscript = after.transcript;
|
|
12978
13099
|
const rawOutput = extractStudioReplTranscriptDelta(beforeTranscript, afterTranscript);
|
|
12979
|
-
const output = cleanStudioReplCapturedOutput(rawOutput);
|
|
13100
|
+
const output = cleanStudioReplCapturedOutput(rawOutput, sent.display, sent.completionLine);
|
|
12980
13101
|
const status: StudioReplJournalEntry["status"] = sent.controlFiles?.doneFile
|
|
12981
13102
|
? (completed ? "captured" : "timeout")
|
|
12982
13103
|
: (output.trim() ? "captured" : "sent");
|
|
@@ -12986,7 +13107,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
12986
13107
|
output,
|
|
12987
13108
|
status,
|
|
12988
13109
|
completedAt: Date.now(),
|
|
12989
|
-
});
|
|
13110
|
+
}, currentSession);
|
|
12990
13111
|
const statusLine = sent.controlFiles?.doneFile
|
|
12991
13112
|
? (completed ? "Completed." : `Timed out after ${timeoutMs} ms waiting for completion marker.`)
|
|
12992
13113
|
: "Submitted.";
|
|
@@ -13022,6 +13143,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
13022
13143
|
usedControlFile: sent.usedControlFile,
|
|
13023
13144
|
submissionText: sent.submissionText,
|
|
13024
13145
|
controlFiles: sent.controlFiles,
|
|
13146
|
+
echoMode: sent.display?.mode || "off",
|
|
13147
|
+
submissionAnchorId: sent.display?.enabled ? sent.display.anchorId : undefined,
|
|
13025
13148
|
output,
|
|
13026
13149
|
recordEntryId: journalEntry.id,
|
|
13027
13150
|
recordPath: selected.session.recordPath,
|
|
@@ -13035,17 +13158,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
13035
13158
|
output: error instanceof Error ? error.message : String(error),
|
|
13036
13159
|
status: error instanceof Error && /timed out/i.test(error.message) ? "timeout" : "error",
|
|
13037
13160
|
completedAt: Date.now(),
|
|
13038
|
-
});
|
|
13161
|
+
}, submittedSession || selected.session);
|
|
13039
13162
|
} catch {
|
|
13040
13163
|
// Preserve the execution error when record maintenance also fails.
|
|
13041
13164
|
}
|
|
13042
13165
|
}
|
|
13043
13166
|
throw error;
|
|
13044
13167
|
} finally {
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
}
|
|
13168
|
+
await releaseOrRetainStudioReplSubmission(lease, submittedSession, submittedControlFiles);
|
|
13169
|
+
lease = null;
|
|
13170
|
+
submittedControlFiles = undefined;
|
|
13049
13171
|
}
|
|
13050
13172
|
},
|
|
13051
13173
|
});
|
|
@@ -15914,7 +16036,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
15914
16036
|
let journalEntry: StudioReplJournalEntry | null = null;
|
|
15915
16037
|
let lease: Awaited<ReturnType<typeof acquireReplSessionSendLease>> | null = null;
|
|
15916
16038
|
let submittedSession: StudioReplSessionInfo | null = null;
|
|
15917
|
-
let
|
|
16039
|
+
let submittedControlFiles: StudioReplControlFiles | undefined;
|
|
15918
16040
|
try {
|
|
15919
16041
|
if (!session) throw new Error(`No tmux REPL session named ${msg.sessionName}.`);
|
|
15920
16042
|
if (session.recordId && !session.recordWarning) {
|
|
@@ -15949,17 +16071,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
15949
16071
|
code: msg.text,
|
|
15950
16072
|
status: "sending",
|
|
15951
16073
|
skippedChunks: msg.skippedChunks,
|
|
16074
|
+
}, currentSession);
|
|
16075
|
+
const sent = sendTextToStudioReplSession(msg.sessionName, msg.text, currentSession.target, currentSession.runtime, {
|
|
16076
|
+
submissionId: journalEntry.id,
|
|
16077
|
+
echoMode: msg.echoMode,
|
|
15952
16078
|
});
|
|
15953
|
-
|
|
16079
|
+
submittedControlFiles = sent.controlFiles;
|
|
15954
16080
|
if (!sent.ok) {
|
|
15955
|
-
if (sent.submissionStarted)
|
|
15956
|
-
submittedSession = currentSession;
|
|
15957
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
15958
|
-
}
|
|
16081
|
+
if (sent.submissionStarted) submittedSession = currentSession;
|
|
15959
16082
|
throw new Error(sent.message);
|
|
15960
16083
|
}
|
|
15961
16084
|
submittedSession = currentSession;
|
|
15962
|
-
submittedDoneFile = sent.controlFiles?.doneFile;
|
|
15963
16085
|
studioReplActiveSessionName = msg.sessionName;
|
|
15964
16086
|
sendToClient(client, {
|
|
15965
16087
|
type: "repl_send_ack",
|
|
@@ -15982,29 +16104,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
15982
16104
|
}
|
|
15983
16105
|
const afterTranscript = after.transcript;
|
|
15984
16106
|
const rawOutput = extractStudioReplTranscriptDelta(beforeTranscript, afterTranscript);
|
|
15985
|
-
const output = cleanStudioReplCapturedOutput(rawOutput);
|
|
16107
|
+
const output = cleanStudioReplCapturedOutput(rawOutput, sent.display, sent.completionLine);
|
|
15986
16108
|
updateStudioReplJournalEntryOutput(
|
|
15987
16109
|
msg.requestId,
|
|
15988
16110
|
msg.sessionName,
|
|
15989
16111
|
output,
|
|
15990
16112
|
sent.controlFiles?.doneFile ? (completed ? "captured" : "timeout") : (output.trim() ? "captured" : "sent"),
|
|
16113
|
+
currentSession,
|
|
15991
16114
|
);
|
|
15992
|
-
if (lease) {
|
|
15993
|
-
await releaseOrRetainStudioReplSendLease(lease, submittedSession, submittedDoneFile);
|
|
15994
|
-
lease = null;
|
|
15995
|
-
}
|
|
15996
16115
|
sendReplCaptureToClient(client, msg.sessionName, { requestId: msg.requestId });
|
|
15997
16116
|
} catch (error) {
|
|
15998
16117
|
const message = error instanceof Error ? error.message : String(error);
|
|
15999
|
-
if (journalEntry) updateStudioReplJournalEntryOutput(
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16118
|
+
if (journalEntry) updateStudioReplJournalEntryOutput(
|
|
16119
|
+
msg.requestId,
|
|
16120
|
+
msg.sessionName,
|
|
16121
|
+
message,
|
|
16122
|
+
"error",
|
|
16123
|
+
submittedSession || session,
|
|
16124
|
+
);
|
|
16004
16125
|
sendToClient(client, { type: "error", requestId: msg.requestId, message });
|
|
16005
16126
|
sendReplCaptureToClient(client, msg.sessionName, { requestId: msg.requestId, replError: message });
|
|
16006
16127
|
} finally {
|
|
16007
|
-
await lease
|
|
16128
|
+
await releaseOrRetainStudioReplSubmission(lease, submittedSession, submittedControlFiles);
|
|
16129
|
+
lease = null;
|
|
16130
|
+
submittedControlFiles = undefined;
|
|
16008
16131
|
}
|
|
16009
16132
|
})();
|
|
16010
16133
|
return;
|