@oh-my-pi/pi-coding-agent 16.1.13 → 16.1.15

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.
Files changed (153) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/cli.js +5615 -2833
  3. package/dist/types/advisor/runtime.d.ts +3 -0
  4. package/dist/types/config/settings-schema.d.ts +64 -14
  5. package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
  6. package/dist/types/eval/backend-helpers.d.ts +23 -0
  7. package/dist/types/eval/executor-base.d.ts +118 -0
  8. package/dist/types/eval/index.d.ts +2 -0
  9. package/dist/types/eval/jl/executor.d.ts +44 -0
  10. package/dist/types/eval/jl/index.d.ts +11 -0
  11. package/dist/types/eval/jl/kernel.d.ts +28 -0
  12. package/dist/types/eval/jl/prelude.d.ts +1 -0
  13. package/dist/types/eval/jl/runtime.d.ts +22 -0
  14. package/dist/types/eval/kernel-base.d.ts +105 -0
  15. package/dist/types/eval/py/kernel.d.ts +3 -61
  16. package/dist/types/eval/rb/executor.d.ts +77 -0
  17. package/dist/types/eval/rb/index.d.ts +11 -0
  18. package/dist/types/eval/rb/kernel.d.ts +31 -0
  19. package/dist/types/eval/rb/prelude.d.ts +1 -0
  20. package/dist/types/eval/rb/runtime.d.ts +23 -0
  21. package/dist/types/eval/runtime-env.d.ts +24 -0
  22. package/dist/types/eval/types.d.ts +3 -3
  23. package/dist/types/export/share.d.ts +8 -1
  24. package/dist/types/extensibility/extensions/runner.d.ts +2 -15
  25. package/dist/types/extensibility/hooks/loader.d.ts +1 -25
  26. package/dist/types/extensibility/session-handler-types.d.ts +23 -0
  27. package/dist/types/jsonrpc/message-framing.d.ts +35 -0
  28. package/dist/types/mcp/transports/stdio.d.ts +12 -1
  29. package/dist/types/mnemopi/embed-client.d.ts +7 -24
  30. package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
  31. package/dist/types/modes/components/custom-editor.d.ts +11 -0
  32. package/dist/types/modes/components/selector-helpers.d.ts +53 -0
  33. package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -3
  34. package/dist/types/modes/components/status-line/types.d.ts +1 -0
  35. package/dist/types/modes/interactive-mode.d.ts +0 -2
  36. package/dist/types/modes/theme/theme.d.ts +8 -1
  37. package/dist/types/modes/types.d.ts +0 -2
  38. package/dist/types/modes/utils/copy-targets.d.ts +1 -1
  39. package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
  40. package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
  41. package/dist/types/sdk.d.ts +1 -1
  42. package/dist/types/secrets/obfuscator.d.ts +3 -3
  43. package/dist/types/session/agent-session.d.ts +2 -2
  44. package/dist/types/stt/asr-client.d.ts +3 -29
  45. package/dist/types/subprocess/worker-client.d.ts +149 -0
  46. package/dist/types/subprocess/worker-runtime.d.ts +107 -0
  47. package/dist/types/tiny/title-client.d.ts +14 -34
  48. package/dist/types/tools/eval-backends.d.ts +6 -3
  49. package/dist/types/tools/eval-render.d.ts +6 -5
  50. package/dist/types/tools/eval.d.ts +13 -15
  51. package/dist/types/tools/index.d.ts +3 -3
  52. package/dist/types/tts/tts-client.d.ts +3 -28
  53. package/dist/types/tui/code-cell.d.ts +7 -0
  54. package/dist/types/utils/file-display-mode.d.ts +1 -1
  55. package/dist/types/utils/shell-snapshot.d.ts +10 -0
  56. package/dist/types/web/parallel.d.ts +6 -0
  57. package/dist/types/web/search/providers/perplexity.d.ts +17 -3
  58. package/package.json +12 -12
  59. package/src/advisor/__tests__/advisor.test.ts +114 -0
  60. package/src/advisor/runtime.ts +129 -1
  61. package/src/config/model-registry.ts +12 -4
  62. package/src/config/settings-schema.ts +74 -18
  63. package/src/config/settings.ts +5 -0
  64. package/src/dap/client.ts +13 -107
  65. package/src/eval/__tests__/julia-prelude.test.ts +77 -0
  66. package/src/eval/backend-helpers.ts +48 -0
  67. package/src/eval/executor-base.ts +425 -0
  68. package/src/eval/index.ts +2 -0
  69. package/src/eval/jl/executor.ts +540 -0
  70. package/src/eval/jl/index.ts +54 -0
  71. package/src/eval/jl/kernel.ts +235 -0
  72. package/src/eval/jl/prelude.jl +930 -0
  73. package/src/eval/jl/prelude.ts +3 -0
  74. package/src/eval/jl/runner.jl +634 -0
  75. package/src/eval/jl/runtime.ts +118 -0
  76. package/src/eval/js/index.ts +3 -14
  77. package/src/eval/kernel-base.ts +569 -0
  78. package/src/eval/py/executor.ts +43 -252
  79. package/src/eval/py/index.ts +9 -20
  80. package/src/eval/py/kernel.ts +29 -544
  81. package/src/eval/rb/executor.ts +504 -0
  82. package/src/eval/rb/index.ts +54 -0
  83. package/src/eval/rb/kernel.ts +230 -0
  84. package/src/eval/rb/prelude.rb +721 -0
  85. package/src/eval/rb/prelude.ts +3 -0
  86. package/src/eval/rb/runner.rb +474 -0
  87. package/src/eval/rb/runtime.ts +132 -0
  88. package/src/eval/runtime-env.ts +104 -0
  89. package/src/eval/types.ts +3 -3
  90. package/src/exec/bash-executor.ts +44 -0
  91. package/src/export/share.ts +51 -28
  92. package/src/extensibility/extensions/runner.ts +4 -11
  93. package/src/extensibility/hooks/loader.ts +3 -21
  94. package/src/extensibility/session-handler-types.ts +21 -0
  95. package/src/internal-urls/docs-index.generated.txt +1 -1
  96. package/src/jsonrpc/message-framing.ts +142 -0
  97. package/src/lsp/client.ts +13 -109
  98. package/src/mcp/transports/stdio.ts +20 -4
  99. package/src/mnemopi/embed-client.ts +43 -198
  100. package/src/modes/components/agent-dashboard.ts +17 -40
  101. package/src/modes/components/chat-transcript-builder.ts +18 -102
  102. package/src/modes/components/custom-editor.test.ts +22 -0
  103. package/src/modes/components/custom-editor.ts +29 -1
  104. package/src/modes/components/extensions/extension-dashboard.ts +4 -13
  105. package/src/modes/components/extensions/extension-list.ts +16 -44
  106. package/src/modes/components/footer.ts +4 -3
  107. package/src/modes/components/history-search.ts +4 -16
  108. package/src/modes/components/selector-helpers.ts +129 -0
  109. package/src/modes/components/settings-selector.ts +7 -5
  110. package/src/modes/components/status-line/component.ts +5 -1
  111. package/src/modes/components/status-line/context-thresholds.ts +11 -3
  112. package/src/modes/components/status-line/segments.ts +1 -1
  113. package/src/modes/components/status-line/types.ts +1 -0
  114. package/src/modes/components/tree-selector.ts +13 -18
  115. package/src/modes/controllers/command-controller.ts +1 -0
  116. package/src/modes/controllers/event-controller.ts +3 -9
  117. package/src/modes/controllers/input-controller.ts +32 -54
  118. package/src/modes/interactive-mode.ts +5 -7
  119. package/src/modes/theme/theme.ts +35 -0
  120. package/src/modes/types.ts +0 -2
  121. package/src/modes/utils/copy-targets.ts +3 -2
  122. package/src/modes/utils/interactive-context-helpers.ts +27 -0
  123. package/src/modes/utils/transcript-render-helpers.ts +157 -0
  124. package/src/modes/utils/ui-helpers.ts +21 -126
  125. package/src/prompts/system/system-prompt.md +5 -5
  126. package/src/prompts/tools/bash.md +2 -3
  127. package/src/prompts/tools/eval.md +6 -4
  128. package/src/prompts/tools/find.md +0 -4
  129. package/src/prompts/tools/read.md +1 -2
  130. package/src/prompts/tools/replace.md +1 -1
  131. package/src/sdk.ts +13 -7
  132. package/src/secrets/obfuscator.ts +3 -9
  133. package/src/session/agent-session.ts +42 -9
  134. package/src/slash-commands/builtin-registry.ts +2 -1
  135. package/src/stt/asr-client.ts +35 -215
  136. package/src/stt/asr-worker.ts +29 -181
  137. package/src/subprocess/worker-client.ts +297 -0
  138. package/src/subprocess/worker-runtime.ts +277 -0
  139. package/src/task/executor.ts +4 -4
  140. package/src/tiny/title-client.ts +53 -219
  141. package/src/tiny/worker.ts +29 -180
  142. package/src/tools/eval-backends.ts +10 -3
  143. package/src/tools/eval-render.ts +17 -8
  144. package/src/tools/eval.ts +187 -22
  145. package/src/tools/index.ts +51 -28
  146. package/src/tts/tts-client.ts +38 -206
  147. package/src/tts/tts-worker.ts +23 -97
  148. package/src/tui/code-cell.ts +12 -1
  149. package/src/utils/file-display-mode.ts +2 -3
  150. package/src/utils/shell-snapshot.ts +63 -1
  151. package/src/web/parallel.ts +43 -42
  152. package/src/web/search/providers/parallel.ts +10 -99
  153. package/src/web/search/providers/perplexity.ts +18 -6
@@ -2,22 +2,29 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
4
  import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
5
- import { Settings } from "../../config/settings";
6
- import { OutputSink } from "../../session/streaming-output";
7
5
  import type { ToolSession } from "../../tools";
8
- import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
9
- import { isEvalTimeoutControlEvent } from "../bridge-timeout";
6
+ import {
7
+ attachSessionOwner,
8
+ buildManagedKernelEnv,
9
+ buildManagedKernelEnvPatch,
10
+ createCancelledKernelResult,
11
+ executeWithKernelBase,
12
+ getExecutionDeadlineMs,
13
+ getRemainingTimeoutMs,
14
+ isCancellationError,
15
+ isTimedOutCancellation,
16
+ waitForPromiseWithCancellation,
17
+ } from "../executor-base";
10
18
  import type { JsStatusEvent } from "../js/shared/types";
11
19
  import {
12
20
  checkPythonKernelAvailability,
13
21
  type KernelDisplayOutput,
14
22
  type KernelExecuteOptions,
15
23
  type KernelExecuteResult,
16
- type KernelRuntimeEnv,
17
24
  PythonKernel,
18
25
  } from "./kernel";
19
26
  import { resolveExplicitPythonRuntime } from "./runtime";
20
- import { ensurePyToolBridge, registerPyToolBridge } from "./tool-bridge";
27
+ import { ensurePyToolBridge } from "./tool-bridge";
21
28
 
22
29
  export type PythonKernelMode = "session" | "per-call";
23
30
 
@@ -170,22 +177,11 @@ class PythonExecutionCancelledError extends Error {
170
177
 
171
178
  constructor(timedOut: boolean) {
172
179
  super(timedOut ? "Command timed out" : "Command aborted");
173
- this.name = timedOut ? "TimeoutError" : "AbortError";
180
+ this.name = "PythonExecutionCancelledError";
174
181
  this.timedOut = timedOut;
175
182
  }
176
183
  }
177
184
 
178
- function getExecutionDeadlineMs(options?: Pick<PythonExecutorOptions, "deadlineMs" | "timeoutMs">): number | undefined {
179
- if (options?.deadlineMs !== undefined) return options.deadlineMs;
180
- if (options?.timeoutMs === undefined) return undefined;
181
- return Date.now() + options.timeoutMs;
182
- }
183
-
184
- function getRemainingTimeoutMs(deadlineMs?: number): number | undefined {
185
- if (deadlineMs === undefined) return undefined;
186
- return deadlineMs - Date.now();
187
- }
188
-
189
185
  function requireRemainingTimeoutMs(deadlineMs?: number): number | undefined {
190
186
  const remainingMs = getRemainingTimeoutMs(deadlineMs);
191
187
  if (remainingMs === undefined) return undefined;
@@ -195,64 +191,6 @@ function requireRemainingTimeoutMs(deadlineMs?: number): number | undefined {
195
191
  return remainingMs;
196
192
  }
197
193
 
198
- function isCancellationError(error: unknown): boolean {
199
- return (
200
- error instanceof PythonExecutionCancelledError ||
201
- (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) ||
202
- (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError"))
203
- );
204
- }
205
-
206
- function isTimedOutCancellation(error: unknown, signal?: AbortSignal): boolean {
207
- if (error instanceof PythonExecutionCancelledError) return error.timedOut;
208
- if (error instanceof DOMException) return error.name === "TimeoutError";
209
- if (error instanceof Error && error.name === "TimeoutError") return true;
210
- const reason = signal?.reason;
211
- if (reason instanceof DOMException) return reason.name === "TimeoutError";
212
- return reason instanceof Error ? reason.name === "TimeoutError" : false;
213
- }
214
-
215
- async function waitForPromiseWithCancellation<T>(
216
- promise: Promise<T>,
217
- options: Pick<PythonExecutorOptions, "signal" | "deadlineMs">,
218
- ): Promise<T> {
219
- if (options.signal?.aborted) {
220
- throw new PythonExecutionCancelledError(isTimedOutCancellation(options.signal.reason, options.signal));
221
- }
222
- const remainingMs = getRemainingTimeoutMs(options.deadlineMs);
223
- if (remainingMs !== undefined && remainingMs <= 0) {
224
- throw new PythonExecutionCancelledError(true);
225
- }
226
- if (!options.signal && remainingMs === undefined) {
227
- return await promise;
228
- }
229
-
230
- const { promise: resultPromise, resolve, reject } = Promise.withResolvers<T>();
231
- const cleanups: Array<() => void> = [];
232
- const finish = (cb: () => void): void => {
233
- while (cleanups.length > 0) cleanups.pop()?.();
234
- cb();
235
- };
236
- if (options.signal) {
237
- const onAbort = (): void =>
238
- finish(() =>
239
- reject(new PythonExecutionCancelledError(isTimedOutCancellation(options.signal?.reason, options.signal))),
240
- );
241
- options.signal.addEventListener("abort", onAbort, { once: true });
242
- cleanups.push(() => options.signal?.removeEventListener("abort", onAbort));
243
- }
244
- if (remainingMs !== undefined) {
245
- const timer = setTimeout(() => finish(() => reject(new PythonExecutionCancelledError(true))), remainingMs);
246
- timer.unref();
247
- cleanups.push(() => clearTimeout(timer));
248
- }
249
- promise.then(
250
- value => finish(() => resolve(value)),
251
- err => finish(() => reject(err)),
252
- );
253
- return await resultPromise;
254
- }
255
-
256
194
  // ---------------------------------------------------------------------------
257
195
  // Result formatting
258
196
  // ---------------------------------------------------------------------------
@@ -274,95 +212,24 @@ function formatKernelTimeoutAnnotation(timeoutMs: number | undefined, kernelKill
274
212
 
275
213
  function createCancelledPythonResult(timedOut: boolean, timeoutMs?: number): PythonResult {
276
214
  const output = timedOut ? (formatTimeoutAnnotation(timeoutMs) ?? "Command timed out") : "";
277
- const outputBytes = Buffer.byteLength(output, "utf-8");
278
- const outputLines = output.length > 0 ? 1 : 0;
279
- return {
280
- output,
281
- exitCode: undefined,
282
- cancelled: true,
283
- truncated: false,
284
- totalLines: outputLines,
285
- totalBytes: outputBytes,
286
- outputLines,
287
- outputBytes,
288
- displayOutputs: [],
289
- stdinRequested: false,
290
- };
215
+ return createCancelledKernelResult(output);
291
216
  }
292
217
 
293
218
  // ---------------------------------------------------------------------------
294
219
  // Kernel start helpers
295
220
  // ---------------------------------------------------------------------------
296
221
 
297
- const MANAGED_KERNEL_ENV_KEYS = [
298
- "PI_SESSION_FILE",
299
- "PI_ARTIFACTS_DIR",
300
- "PI_TOOL_BRIDGE_URL",
301
- "PI_TOOL_BRIDGE_TOKEN",
302
- "PI_TOOL_BRIDGE_SESSION",
303
- "PI_EVAL_LOCAL_ROOTS",
304
- ] as const;
305
-
306
- function buildKernelEnvPatch(options: {
307
- sessionFile?: string;
308
- artifactsDir?: string;
309
- bridgeSessionId?: string;
310
- bridge?: { url: string; token: string };
311
- localRoots?: Record<string, string>;
312
- }): KernelRuntimeEnv {
313
- const localRoots = options.localRoots;
314
- return {
315
- PI_SESSION_FILE: options.sessionFile ?? null,
316
- PI_ARTIFACTS_DIR: options.artifactsDir ?? null,
317
- PI_TOOL_BRIDGE_URL: options.bridge?.url ?? null,
318
- PI_TOOL_BRIDGE_TOKEN: options.bridge?.token ?? null,
319
- PI_TOOL_BRIDGE_SESSION: options.bridge && options.bridgeSessionId ? options.bridgeSessionId : null,
320
- PI_EVAL_LOCAL_ROOTS: localRoots && Object.keys(localRoots).length > 0 ? JSON.stringify(localRoots) : null,
321
- };
322
- }
323
-
324
- function buildKernelEnv(options: {
325
- sessionFile?: string;
326
- artifactsDir?: string;
327
- bridgeSessionId?: string;
328
- bridge?: { url: string; token: string };
329
- localRoots?: Record<string, string>;
330
- }): Record<string, string> | undefined {
331
- const patch = buildKernelEnvPatch(options);
332
- const env: Record<string, string> = {};
333
- for (const key of MANAGED_KERNEL_ENV_KEYS) {
334
- const value = patch[key];
335
- if (value !== null) env[key] = value;
336
- }
337
- return Object.keys(env).length > 0 ? env : undefined;
338
- }
339
-
340
222
  async function startKernel(cwd: string, options: PythonExecutorOptions): Promise<PythonKernel> {
341
223
  requireRemainingTimeoutMs(options.deadlineMs);
342
224
  return await PythonKernel.start({
343
225
  cwd,
344
- env: buildKernelEnv(options),
226
+ env: buildManagedKernelEnv(options),
345
227
  signal: options.signal,
346
228
  deadlineMs: options.deadlineMs,
347
229
  interpreter: options.interpreter,
348
230
  });
349
231
  }
350
232
 
351
- function attachOwner(session: PythonSession, sessionId: string, ownerId: string | undefined): void {
352
- if (ownerId !== undefined) {
353
- if (session.hasFallbackOwner) {
354
- session.ownerIds.delete(sessionId);
355
- session.hasFallbackOwner = false;
356
- }
357
- session.ownerIds.add(ownerId);
358
- return;
359
- }
360
- if (session.hasFallbackOwner || session.ownerIds.size === 0) {
361
- session.ownerIds.add(sessionId);
362
- session.hasFallbackOwner = true;
363
- }
364
- }
365
-
366
233
  async function acquireSession(
367
234
  sessionKey: string,
368
235
  sessionId: string,
@@ -371,13 +238,13 @@ async function acquireSession(
371
238
  ): Promise<PythonSession> {
372
239
  const existing = sessions.get(sessionKey);
373
240
  if (existing) {
374
- attachOwner(existing, sessionId, options.kernelOwnerId);
241
+ attachSessionOwner(existing, sessionId, options.kernelOwnerId);
375
242
  return existing;
376
243
  }
377
244
  const starting = startingSessions.get(sessionKey);
378
245
  if (starting) {
379
246
  const session = await starting;
380
- attachOwner(session, sessionId, options.kernelOwnerId);
247
+ attachSessionOwner(session, sessionId, options.kernelOwnerId);
381
248
  return session;
382
249
  }
383
250
  const startup = (async () => {
@@ -396,7 +263,7 @@ async function acquireSession(
396
263
  startingSessions.set(sessionKey, startup);
397
264
  try {
398
265
  const session = await startup;
399
- attachOwner(session, sessionId, options.kernelOwnerId);
266
+ attachSessionOwner(session, sessionId, options.kernelOwnerId);
400
267
  return session;
401
268
  } finally {
402
269
  if (startingSessions.get(sessionKey) === startup) startingSessions.delete(sessionKey);
@@ -507,108 +374,24 @@ async function executeWithKernel(
507
374
  code: string,
508
375
  options: PythonExecutorOptions | undefined,
509
376
  ): Promise<PythonResult> {
510
- const settings = await Settings.init();
511
- const sink = new OutputSink({
512
- onChunk: options?.onChunk,
513
- artifactPath: options?.artifactPath,
514
- artifactId: options?.artifactId,
515
- headBytes: resolveOutputSinkHeadBytes(settings),
516
- maxColumns: resolveOutputMaxColumns(settings),
377
+ return executeWithKernelBase<PythonExecutorOptions>({
378
+ kernel,
379
+ code,
380
+ options,
381
+ runIdPrefix: "py",
382
+ errorLogLabel: "Python",
383
+ cancelledErrorClass: PythonExecutionCancelledError,
384
+ buildKernelEnvPatch: buildManagedKernelEnvPatch,
385
+ formatKernelTimeoutAnnotation,
386
+ formatTimeoutAnnotation,
517
387
  });
518
- const displayOutputs: KernelDisplayOutput[] = [];
519
- const deadlineMs = getExecutionDeadlineMs(options);
520
- let executionTimeoutMs: number | undefined;
521
-
522
- // Collect every display output and, for status events, stream them live so
523
- // long-running bridge helpers (e.g. `agent()`) surface progress mid-cell.
524
- const collectDisplay = (output: KernelDisplayOutput) => {
525
- if (output.type === "status") {
526
- // Timeout-control events drive the eval watchdog only; never store or
527
- // render them as cell output.
528
- options?.onStatus?.(output.event);
529
- if (isEvalTimeoutControlEvent(output.event)) return;
530
- }
531
- displayOutputs.push(output);
532
- };
533
- const emitStatus = options?.emitStatus ?? ((event: JsStatusEvent) => collectDisplay({ type: "status", event }));
534
- const runId = `py-${crypto.randomUUID()}`;
535
- const unregisterBridge =
536
- options?.toolSession && options?.bridgeSessionId
537
- ? registerPyToolBridge(options.bridgeSessionId, runId, {
538
- toolSession: options.toolSession,
539
- signal: options.signal,
540
- emitStatus,
541
- })
542
- : null;
543
-
544
- try {
545
- executionTimeoutMs = requireRemainingTimeoutMs(deadlineMs);
546
- const result = await kernel.execute(code, {
547
- cwd: options?.cwd,
548
- env: buildKernelEnvPatch(options ?? {}),
549
- id: runId,
550
- signal: options?.signal,
551
- timeoutMs: executionTimeoutMs,
552
- onChunk: text => sink.push(text),
553
- onDisplay: output => collectDisplay(output),
554
- });
555
-
556
- if (result.cancelled) {
557
- const annotation = result.timedOut
558
- ? formatKernelTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, result.kernelKilled ?? false)
559
- : undefined;
560
- return {
561
- exitCode: undefined,
562
- cancelled: true,
563
- displayOutputs,
564
- stdinRequested: result.stdinRequested,
565
- ...(await sink.dump(annotation)),
566
- };
567
- }
568
-
569
- if (result.stdinRequested) {
570
- return {
571
- exitCode: 1,
572
- cancelled: false,
573
- displayOutputs,
574
- stdinRequested: true,
575
- ...(await sink.dump("Kernel requested stdin; interactive input is not supported.")),
576
- };
577
- }
578
-
579
- const exitCode = result.status === "ok" ? 0 : 1;
580
- return {
581
- exitCode,
582
- cancelled: false,
583
- displayOutputs,
584
- stdinRequested: false,
585
- ...(await sink.dump()),
586
- };
587
- } catch (err) {
588
- if (isCancellationError(err) || options?.signal?.aborted) {
589
- const timedOut = isTimedOutCancellation(err, options?.signal);
590
- return {
591
- exitCode: undefined,
592
- cancelled: true,
593
- displayOutputs,
594
- stdinRequested: false,
595
- ...(await sink.dump(
596
- timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs) : undefined,
597
- )),
598
- };
599
- }
600
- const error = err instanceof Error ? err : new Error(String(err));
601
- logger.error("Python execution failed", { error: error.message });
602
- throw error;
603
- } finally {
604
- unregisterBridge?.();
605
- }
606
388
  }
607
389
 
608
390
  async function ensureKernelAvailable(cwd: string, options: PythonExecutorOptions): Promise<void> {
609
391
  const availability = await waitForPromiseWithCancellation(
610
392
  checkPythonKernelAvailability(cwd, options.interpreter),
611
393
  options,
394
+ PythonExecutionCancelledError,
612
395
  );
613
396
  if (!availability.ok) {
614
397
  throw new Error(availability.reason ?? "Python kernel unavailable");
@@ -671,7 +454,9 @@ async function executeOnSession(code: string, cwd: string, options: PythonExecut
671
454
  }
672
455
  const session = await acquireSession(sessionKey, sessionId, cwd, options);
673
456
  if (options.signal?.aborted) {
674
- throw new PythonExecutionCancelledError(isTimedOutCancellation(options.signal.reason, options.signal));
457
+ throw new PythonExecutionCancelledError(
458
+ isTimedOutCancellation(options.signal.reason, PythonExecutionCancelledError, options.signal),
459
+ );
675
460
  }
676
461
  if (sessions.get(session.sessionKey) !== session) {
677
462
  throw new PythonExecutionCancelledError(false);
@@ -686,7 +471,7 @@ async function executeOnSession(code: string, cwd: string, options: PythonExecut
686
471
  try {
687
472
  return await executeWithKernel(session.kernel, code, runOptions);
688
473
  } catch (err) {
689
- if (isCancellationError(err) || options.signal?.aborted) throw err;
474
+ if (isCancellationError(err, PythonExecutionCancelledError) || options.signal?.aborted) throw err;
690
475
  if (session.kernel.isAlive()) throw err;
691
476
  if (sessions.get(session.sessionKey) !== session) {
692
477
  throw new PythonExecutionCancelledError(false);
@@ -722,7 +507,11 @@ export async function executePython(code: string, options?: PythonExecutorOption
722
507
  requireRemainingTimeoutMs(deadlineMs);
723
508
  if (executionOptions.signal?.aborted) {
724
509
  throw new PythonExecutionCancelledError(
725
- isTimedOutCancellation(executionOptions.signal.reason, executionOptions.signal),
510
+ isTimedOutCancellation(
511
+ executionOptions.signal.reason,
512
+ PythonExecutionCancelledError,
513
+ executionOptions.signal,
514
+ ),
726
515
  );
727
516
  }
728
517
  await ensureKernelAvailable(cwd, executionOptions);
@@ -734,8 +523,10 @@ export async function executePython(code: string, options?: PythonExecutorOption
734
523
  }
735
524
  return await executeOnSession(code, cwd, executionOptions);
736
525
  } catch (err) {
737
- if (isCancellationError(err) || executionOptions.signal?.aborted) {
738
- return createCancelledPythonResult(isTimedOutCancellation(err, executionOptions.signal));
526
+ if (isCancellationError(err, PythonExecutionCancelledError) || executionOptions.signal?.aborted) {
527
+ return createCancelledPythonResult(
528
+ isTimedOutCancellation(err, PythonExecutionCancelledError, executionOptions.signal),
529
+ );
739
530
  }
740
531
  throw err;
741
532
  }
@@ -5,24 +5,24 @@ import {
5
5
  type ExecutorBackendResult,
6
6
  resolveEvalUrlRoots,
7
7
  } from "../backend";
8
+ import {
9
+ readSetting,
10
+ namespaceSessionId as sharedNamespace,
11
+ readInterpreterSetting as sharedReadInterpreterSetting,
12
+ toExecutorBackendResult,
13
+ } from "../backend-helpers";
8
14
  import { executePython, type PythonExecutorOptions } from "./executor";
9
15
  import { checkPythonKernelAvailability } from "./kernel";
10
16
 
11
17
  const PYTHON_SESSION_PREFIX = "python:";
12
18
 
13
19
  export function namespaceSessionId(sessionId: string): string {
14
- return sessionId.startsWith(PYTHON_SESSION_PREFIX) ? sessionId : `${PYTHON_SESSION_PREFIX}${sessionId}`;
15
- }
16
-
17
- function readSetting<T>(session: ToolSession, key: string): T | undefined {
18
- const settings = session.settings as { get?: (key: string) => T | undefined } | undefined;
19
- return settings?.get?.(key);
20
+ return sharedNamespace(sessionId, PYTHON_SESSION_PREFIX);
20
21
  }
21
22
 
22
23
  function readInterpreterSetting(session: ToolSession): string | undefined {
23
- return readSetting<string>(session, "python.interpreter")?.trim() || undefined;
24
+ return sharedReadInterpreterSetting(session, "python.interpreter");
24
25
  }
25
-
26
26
  export default {
27
27
  id: "python",
28
28
  label: "Python",
@@ -52,17 +52,6 @@ export default {
52
52
  toolSession: opts.session,
53
53
  };
54
54
  const result = await executePython(code, executorOptions);
55
- return {
56
- output: result.output,
57
- exitCode: result.exitCode,
58
- cancelled: result.cancelled,
59
- truncated: result.truncated,
60
- artifactId: result.artifactId,
61
- totalLines: result.totalLines,
62
- totalBytes: result.totalBytes,
63
- outputLines: result.outputLines,
64
- outputBytes: result.outputBytes,
65
- displayOutputs: result.displayOutputs,
66
- };
55
+ return toExecutorBackendResult(result);
67
56
  },
68
57
  } satisfies ExecutorBackend;