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

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 (129) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli.js +5547 -2759
  3. package/dist/types/config/settings-schema.d.ts +44 -14
  4. package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
  5. package/dist/types/eval/backend-helpers.d.ts +23 -0
  6. package/dist/types/eval/executor-base.d.ts +118 -0
  7. package/dist/types/eval/index.d.ts +2 -0
  8. package/dist/types/eval/jl/executor.d.ts +44 -0
  9. package/dist/types/eval/jl/index.d.ts +11 -0
  10. package/dist/types/eval/jl/kernel.d.ts +28 -0
  11. package/dist/types/eval/jl/prelude.d.ts +1 -0
  12. package/dist/types/eval/jl/runtime.d.ts +22 -0
  13. package/dist/types/eval/kernel-base.d.ts +105 -0
  14. package/dist/types/eval/py/kernel.d.ts +3 -61
  15. package/dist/types/eval/rb/executor.d.ts +77 -0
  16. package/dist/types/eval/rb/index.d.ts +11 -0
  17. package/dist/types/eval/rb/kernel.d.ts +31 -0
  18. package/dist/types/eval/rb/prelude.d.ts +1 -0
  19. package/dist/types/eval/rb/runtime.d.ts +23 -0
  20. package/dist/types/eval/runtime-env.d.ts +24 -0
  21. package/dist/types/eval/types.d.ts +3 -3
  22. package/dist/types/extensibility/extensions/runner.d.ts +2 -15
  23. package/dist/types/extensibility/hooks/loader.d.ts +1 -25
  24. package/dist/types/extensibility/session-handler-types.d.ts +23 -0
  25. package/dist/types/jsonrpc/message-framing.d.ts +35 -0
  26. package/dist/types/mnemopi/embed-client.d.ts +7 -24
  27. package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
  28. package/dist/types/modes/components/custom-editor.d.ts +11 -0
  29. package/dist/types/modes/components/selector-helpers.d.ts +53 -0
  30. package/dist/types/modes/interactive-mode.d.ts +0 -2
  31. package/dist/types/modes/theme/theme.d.ts +8 -1
  32. package/dist/types/modes/types.d.ts +0 -2
  33. package/dist/types/modes/utils/copy-targets.d.ts +1 -1
  34. package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
  35. package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
  36. package/dist/types/sdk.d.ts +1 -1
  37. package/dist/types/session/agent-session.d.ts +2 -2
  38. package/dist/types/stt/asr-client.d.ts +3 -29
  39. package/dist/types/subprocess/worker-client.d.ts +149 -0
  40. package/dist/types/subprocess/worker-runtime.d.ts +107 -0
  41. package/dist/types/tiny/title-client.d.ts +14 -34
  42. package/dist/types/tools/eval-backends.d.ts +6 -3
  43. package/dist/types/tools/eval-render.d.ts +6 -5
  44. package/dist/types/tools/eval.d.ts +13 -15
  45. package/dist/types/tools/index.d.ts +3 -3
  46. package/dist/types/tts/tts-client.d.ts +3 -28
  47. package/dist/types/tui/code-cell.d.ts +7 -0
  48. package/dist/types/utils/file-display-mode.d.ts +1 -1
  49. package/dist/types/web/parallel.d.ts +6 -0
  50. package/package.json +12 -12
  51. package/src/config/settings-schema.ts +50 -18
  52. package/src/config/settings.ts +5 -0
  53. package/src/dap/client.ts +13 -107
  54. package/src/eval/__tests__/julia-prelude.test.ts +77 -0
  55. package/src/eval/backend-helpers.ts +48 -0
  56. package/src/eval/executor-base.ts +425 -0
  57. package/src/eval/index.ts +2 -0
  58. package/src/eval/jl/executor.ts +540 -0
  59. package/src/eval/jl/index.ts +54 -0
  60. package/src/eval/jl/kernel.ts +235 -0
  61. package/src/eval/jl/prelude.jl +930 -0
  62. package/src/eval/jl/prelude.ts +3 -0
  63. package/src/eval/jl/runner.jl +634 -0
  64. package/src/eval/jl/runtime.ts +118 -0
  65. package/src/eval/js/index.ts +3 -14
  66. package/src/eval/kernel-base.ts +569 -0
  67. package/src/eval/py/executor.ts +43 -252
  68. package/src/eval/py/index.ts +9 -20
  69. package/src/eval/py/kernel.ts +29 -544
  70. package/src/eval/rb/executor.ts +504 -0
  71. package/src/eval/rb/index.ts +54 -0
  72. package/src/eval/rb/kernel.ts +230 -0
  73. package/src/eval/rb/prelude.rb +721 -0
  74. package/src/eval/rb/prelude.ts +3 -0
  75. package/src/eval/rb/runner.rb +474 -0
  76. package/src/eval/rb/runtime.ts +132 -0
  77. package/src/eval/runtime-env.ts +104 -0
  78. package/src/eval/types.ts +3 -3
  79. package/src/extensibility/extensions/runner.ts +4 -11
  80. package/src/extensibility/hooks/loader.ts +3 -21
  81. package/src/extensibility/session-handler-types.ts +21 -0
  82. package/src/internal-urls/docs-index.generated.txt +1 -1
  83. package/src/jsonrpc/message-framing.ts +142 -0
  84. package/src/lsp/client.ts +13 -109
  85. package/src/mnemopi/embed-client.ts +43 -198
  86. package/src/modes/components/agent-dashboard.ts +17 -40
  87. package/src/modes/components/chat-transcript-builder.ts +18 -102
  88. package/src/modes/components/custom-editor.ts +19 -0
  89. package/src/modes/components/extensions/extension-dashboard.ts +4 -13
  90. package/src/modes/components/extensions/extension-list.ts +16 -44
  91. package/src/modes/components/history-search.ts +4 -16
  92. package/src/modes/components/selector-helpers.ts +129 -0
  93. package/src/modes/components/settings-selector.ts +7 -5
  94. package/src/modes/components/tree-selector.ts +13 -18
  95. package/src/modes/controllers/event-controller.ts +3 -9
  96. package/src/modes/controllers/input-controller.ts +32 -54
  97. package/src/modes/interactive-mode.ts +5 -7
  98. package/src/modes/theme/theme.ts +35 -0
  99. package/src/modes/types.ts +0 -2
  100. package/src/modes/utils/copy-targets.ts +3 -2
  101. package/src/modes/utils/interactive-context-helpers.ts +27 -0
  102. package/src/modes/utils/transcript-render-helpers.ts +157 -0
  103. package/src/modes/utils/ui-helpers.ts +21 -126
  104. package/src/prompts/system/system-prompt.md +10 -10
  105. package/src/prompts/tools/bash.md +0 -1
  106. package/src/prompts/tools/eval.md +6 -4
  107. package/src/prompts/tools/find.md +0 -4
  108. package/src/prompts/tools/read.md +1 -2
  109. package/src/prompts/tools/replace.md +1 -1
  110. package/src/prompts/tools/search.md +0 -1
  111. package/src/sdk.ts +13 -7
  112. package/src/session/agent-session.ts +9 -7
  113. package/src/stt/asr-client.ts +35 -215
  114. package/src/stt/asr-worker.ts +29 -181
  115. package/src/subprocess/worker-client.ts +297 -0
  116. package/src/subprocess/worker-runtime.ts +277 -0
  117. package/src/task/executor.ts +4 -4
  118. package/src/tiny/title-client.ts +53 -219
  119. package/src/tiny/worker.ts +29 -180
  120. package/src/tools/eval-backends.ts +10 -3
  121. package/src/tools/eval-render.ts +17 -8
  122. package/src/tools/eval.ts +187 -22
  123. package/src/tools/index.ts +51 -28
  124. package/src/tts/tts-client.ts +38 -206
  125. package/src/tts/tts-worker.ts +23 -97
  126. package/src/tui/code-cell.ts +12 -1
  127. package/src/utils/file-display-mode.ts +2 -3
  128. package/src/web/parallel.ts +43 -42
  129. package/src/web/search/providers/parallel.ts +10 -99
@@ -39,16 +39,20 @@ import type { CompactionQueuedMessage, InteractiveModeContext } from "../../mode
39
39
  import {
40
40
  BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE,
41
41
  type CustomMessage,
42
- isSilentAbort,
43
42
  LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
44
- resolveAbortLabel,
45
43
  SKILL_PROMPT_MESSAGE_TYPE,
46
44
  type SkillPromptDetails,
47
45
  } from "../../session/messages";
48
46
  import type { SessionContext } from "../../session/session-context";
49
- import { createIrcMessageCard } from "../../tools/irc";
50
- import { formatBytes, formatDuration } from "../../tools/render-utils";
51
- import { canonicalizeMessage } from "../../utils/thinking-display";
47
+ import { createAssistantMessageComponent } from "./interactive-context-helpers";
48
+ import {
49
+ assistantHasVisibleContent,
50
+ buildAsyncResultBlock,
51
+ buildFileMentionBlock,
52
+ buildIrcMessageCard,
53
+ normalizeToolArgs,
54
+ resolveAssistantErrorMessage,
55
+ } from "./transcript-render-helpers";
52
56
 
53
57
  type TextBlock = { type: "text"; text: string };
54
58
  interface RenderInitialMessagesOptions {
@@ -143,47 +147,7 @@ export class UiHelpers {
143
147
  case "custom": {
144
148
  if (message.display) {
145
149
  if (message.customType === "async-result") {
146
- const details = (
147
- message as CustomMessage<{
148
- jobId?: string;
149
- type?: "bash" | "task";
150
- label?: string;
151
- durationMs?: number;
152
- jobs?: Array<{
153
- jobId?: string;
154
- type?: "bash" | "task";
155
- label?: string;
156
- durationMs?: number;
157
- }>;
158
- }>
159
- ).details;
160
- const jobs =
161
- details?.jobs && details.jobs.length > 0
162
- ? details.jobs
163
- : [
164
- {
165
- jobId: details?.jobId,
166
- type: details?.type,
167
- label: details?.label,
168
- durationMs: details?.durationMs,
169
- },
170
- ];
171
- const block = new TranscriptBlock();
172
- for (const job of jobs) {
173
- const jobId = job.jobId ?? "unknown";
174
- const typeLabel = job.type ? `[${job.type}]` : "[job]";
175
- const duration = typeof job.durationMs === "number" ? formatDuration(job.durationMs) : undefined;
176
- const line = [
177
- theme.fg("success", `${theme.status.done} Background job completed`),
178
- theme.fg("dim", typeLabel),
179
- theme.fg("accent", jobId),
180
- duration ? theme.fg("dim", `(${duration})`) : undefined,
181
- ]
182
- .filter(Boolean)
183
- .join(" ");
184
- block.addChild(new Text(line, 1, 0));
185
- }
186
- this.ctx.chatContainer.addChild(block);
150
+ this.ctx.chatContainer.addChild(buildAsyncResultBlock(message));
187
151
  break;
188
152
  }
189
153
  if (message.customType === LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE) {
@@ -213,33 +177,7 @@ export class UiHelpers {
213
177
  message.customType === "irc:autoreply" ||
214
178
  message.customType === "irc:relay"
215
179
  ) {
216
- const details = (
217
- message as CustomMessage<{
218
- from?: string;
219
- to?: string;
220
- message?: string;
221
- body?: string;
222
- replyTo?: string;
223
- }>
224
- ).details;
225
- const kind =
226
- message.customType === "irc:incoming"
227
- ? ("incoming" as const)
228
- : message.customType === "irc:autoreply"
229
- ? ("autoreply" as const)
230
- : ("relay" as const);
231
- const card = createIrcMessageCard(
232
- {
233
- kind,
234
- from: details?.from,
235
- to: details?.to,
236
- body: kind === "incoming" ? details?.message : details?.body,
237
- replyTo: details?.replyTo,
238
- timestamp: message.timestamp,
239
- },
240
- () => this.ctx.toolOutputExpanded,
241
- theme,
242
- );
180
+ const card = buildIrcMessageCard(message, () => this.ctx.toolOutputExpanded);
243
181
  this.ctx.chatContainer.addChild(card);
244
182
  return [card];
245
183
  }
@@ -284,25 +222,7 @@ export class UiHelpers {
284
222
  }
285
223
  case "fileMention": {
286
224
  // Render compact file mention display
287
- const block = new TranscriptBlock();
288
- for (const file of message.files) {
289
- let suffix: string;
290
- if (file.skippedReason === "tooLarge") {
291
- const size = typeof file.byteSize === "number" ? formatBytes(file.byteSize) : "unknown size";
292
- suffix = `(skipped: ${size})`;
293
- } else {
294
- suffix = file.image
295
- ? "(image)"
296
- : file.lineCount === undefined
297
- ? "(unknown lines)"
298
- : `(${file.lineCount} lines)`;
299
- }
300
- const text = `${theme.fg("dim", `${theme.tree.last} `)}${theme.fg("muted", "Read")} ${theme.fg(
301
- "accent",
302
- file.path,
303
- )} ${theme.fg("dim", suffix)}`;
304
- block.addChild(new Text(text, 0, 0));
305
- }
225
+ const block = buildFileMentionBlock(message.files, 0);
306
226
  if (block.children.length > 0) this.ctx.chatContainer.addChild(block);
307
227
  break;
308
228
  }
@@ -326,14 +246,7 @@ export class UiHelpers {
326
246
  break;
327
247
  }
328
248
  case "assistant": {
329
- const assistantComponent = new AssistantMessageComponent(
330
- message,
331
- this.ctx.hideThinkingBlock,
332
- () => this.ctx.ui.requestRender(),
333
- this.ctx.viewSession.extensionRunner?.getAssistantThinkingRenderers(),
334
- this.ctx.ui.imageBudget,
335
- this.ctx.proseOnlyThinking,
336
- );
249
+ const assistantComponent = createAssistantMessageComponent(this.ctx, message);
337
250
  this.ctx.chatContainer.addChild(assistantComponent);
338
251
  break;
339
252
  }
@@ -425,11 +338,7 @@ export class UiHelpers {
425
338
  this.ctx.lastAssistantUsage = usage;
426
339
  }
427
340
  }
428
- const hasVisibleAssistantContent = message.content.some(
429
- content =>
430
- (content.type === "text" && canonicalizeMessage(content.text)) ||
431
- (content.type === "thinking" && canonicalizeMessage(content.thinking)),
432
- );
341
+ const hasVisibleAssistantContent = assistantHasVisibleContent(message);
433
342
  if (hasVisibleAssistantContent) {
434
343
  // Rebuild reconstructs immutable history; seal (not finalize) so the
435
344
  // group freezes even if a read's result was never persisted —
@@ -438,14 +347,10 @@ export class UiHelpers {
438
347
  readGroup?.seal();
439
348
  readGroup = null;
440
349
  }
441
- const isAbortedSilently = message.stopReason === "aborted" && isSilentAbort(message.errorMessage);
442
- const hasErrorStop =
443
- !isAbortedSilently && (message.stopReason === "aborted" || message.stopReason === "error");
444
- const errorMessage = hasErrorStop
445
- ? message.stopReason === "aborted"
446
- ? resolveAbortLabel(message.errorMessage, this.ctx.viewSession.retryAttempt)
447
- : message.errorMessage || "Error"
448
- : null;
350
+ const { hasErrorStop, errorMessage } = resolveAssistantErrorMessage(
351
+ message,
352
+ this.ctx.viewSession.retryAttempt,
353
+ );
449
354
 
450
355
  // Render tool call components
451
356
  for (const content of message.content) {
@@ -474,10 +379,7 @@ export class UiHelpers {
474
379
  content.id,
475
380
  );
476
381
  } else {
477
- const normalizedArgs =
478
- content.arguments && typeof content.arguments === "object" && !Array.isArray(content.arguments)
479
- ? (content.arguments as Record<string, unknown>)
480
- : {};
382
+ const normalizedArgs = normalizeToolArgs(content.arguments);
481
383
  readToolCallArgs.set(content.id, normalizedArgs);
482
384
  if (assistantComponent) {
483
385
  readToolCallAssistantComponents.set(content.id, assistantComponent);
@@ -647,10 +549,7 @@ export class UiHelpers {
647
549
  }
648
550
 
649
551
  clearEditor(): void {
650
- this.ctx.editor.setText("");
651
- this.ctx.pendingImages = [];
652
- this.ctx.pendingImageLinks = [];
653
- this.ctx.editor.imageLinks = undefined;
552
+ this.ctx.editor.clearDraft();
654
553
  this.ctx.ui.requestRender();
655
554
  }
656
555
 
@@ -719,11 +618,7 @@ export class UiHelpers {
719
618
  queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void {
720
619
  const queuedImages = images && images.length > 0 ? images : undefined;
721
620
  this.ctx.compactionQueuedMessages.push({ text, mode, images: queuedImages } as CompactionQueuedMessage);
722
- this.ctx.editor.addToHistory(text);
723
- this.ctx.editor.setText("");
724
- this.ctx.editor.imageLinks = undefined;
725
- this.ctx.pendingImages = [];
726
- this.ctx.pendingImageLinks = [];
621
+ this.ctx.editor.clearDraft(text);
727
622
  this.ctx.updatePendingMessagesDisplay();
728
623
  this.ctx.showStatus(
729
624
  queuedImages ? "Queued message with image for after compaction" : "Queued message for after compaction",
@@ -101,17 +101,17 @@ Use tools whenever they improve correctness, completeness, or grounding.
101
101
  {{#if secretsEnabled}}- Redacted `#XXXX#` tokens in output are opaque strings.{{/if}}
102
102
  {{#has tools "inspect_image"}}- Image tasks: prefer `{{toolRefs.inspect_image}}` over `{{toolRefs.read}}` to spare session context.{{/has}}
103
103
 
104
- # Specialized Tool Priority
105
- You MUST use the specialized tool over its shell equivalent:
106
- {{#has tools "read"}}- File or directory reads → `{{toolRefs.read}}`, not `cat` or `ls` (a directory path lists entries).{{/has}}
107
- {{#has tools "edit"}}- Surgical edits → `{{toolRefs.edit}}`, not `sed`.{{/has}}
108
- {{#has tools "write"}}- Create or overwrite → `{{toolRefs.write}}`, not shell redirection.{{/has}}
109
- {{#has tools "lsp"}}- Code intelligence → `{{toolRefs.lsp}}`, not blind search.{{/has}}
110
- {{#has tools "search"}}- Regex search → `{{toolRefs.search}}`, not `grep`, `rg`, or `awk`.{{/has}}
111
- {{#has tools "find"}}- Globbing → `{{toolRefs.find}}`, not `ls **/*.ext` or `fd`.{{/has}}
104
+ # Specialized Tools
105
+ Dedicated tools add gitignore semantics, structured output, and line anchors a raw shell command lacks — reach for them first when they fit:
106
+ {{#has tools "read"}}- File or directory reads → `{{toolRefs.read}}` (a directory path lists entries).{{/has}}
107
+ {{#has tools "edit"}}- Surgical edits → `{{toolRefs.edit}}`.{{/has}}
108
+ {{#has tools "write"}}- Create or overwrite → `{{toolRefs.write}}`.{{/has}}
109
+ {{#has tools "lsp"}}- Code intelligence → `{{toolRefs.lsp}}`.{{/has}}
110
+ {{#has tools "search"}}- Regex search → `{{toolRefs.search}}`.{{/has}}
111
+ {{#has tools "find"}}- Globbing → `{{toolRefs.find}}`.{{/has}}
112
112
  {{#has tools "eval"}}- Quick compute → `{{toolRefs.eval}}`; you SHOULD go step by step.{{/has}}
113
- {{#has tools "bash"}}- Use `{{toolRefs.bash}}` for terminal work—builds, tests, git, package managers—and pipelines that COMPUTE a fact: `wc -l`, `sort | uniq -c`, `comm`, `diff a b`, checksums. Commands shadowing the tools above are blocked.
114
- - Litmus: produces a count, frequency, set difference, or checksum no tool returns → bash. Merely moves, pages, or trims bytes a tool can fetch → use the tool.{{/has}}
113
+ {{#has tools "bash"}}- Use `{{toolRefs.bash}}` for terminal work—builds, tests, git, package managers—and pipelines that COMPUTE a fact: `wc -l`, `sort | uniq -c`, `comm`, `diff a b`, checksums.
114
+ - Litmus: produces a count, frequency, set difference, or checksum no tool returns → bash. Otherwise a dedicated tool usually fits.{{/has}}
115
115
 
116
116
  {{#has tools "report_tool_issue"}}
117
117
  <critical>
@@ -14,7 +14,6 @@ Runs bash in a shell session — terminal ops: git, bun, cargo, python.
14
14
  </instruction>
15
15
 
16
16
  <critical>
17
- - NEVER shell out to fetch, display, list, page, or search what a dedicated tool serves: `cat`/`head`/`tail`/`less`/`more`/`ls` → `read`; `grep`/`rg`/`ag`/`ack` → `search`; `find`/`fd` → `find`; `sed -i`/`perl -i`/`awk -i` → `edit`; `echo >`/heredoc → `write`. Tools keep gitignore semantics, line anchors, structured output shell loses.
18
17
  - NEVER trim or silence output: no `| head -n N`, `| tail -n N`, `| less`, `2>&1`, `2>/dev/null`. stderr already merged; long output auto-truncated, FULL capture kept at `artifact://<id>`.
19
18
  - Pipelines that COMPUTE a new fact are correct bash: `wc -l`, `sort | uniq -c`, `comm`, `cut`, `diff a b`, `shasum`. Litmus: produces a count, frequency table, set difference, or checksum no tool returns → bash. Merely moves or trims bytes a tool can fetch → use the tool.
20
19
  </critical>
@@ -5,7 +5,7 @@ Cells run in array order. State persists per language across cells, tool calls,
5
5
 
6
6
  Cell fields:
7
7
 
8
- - `language` — {{#if py}}`"py"` IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` persistent JavaScript VM{{/if}}.
8
+ - `language` — {{#if py}}`"py"` IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` persistent JavaScript VM{{/if}}{{#if rb}}{{#ifAny py js}}, {{/ifAny}}`"rb"` persistent Ruby kernel{{/if}}{{#if jl}}{{#ifAny py js rb}}, {{/ifAny}}`"jl"` persistent Julia kernel{{/if}}.
9
9
  - `code` — cell body, verbatim. Newlines/quotes JSON-encoded; no fences, no headers.
10
10
  - `title` (optional) — short transcript label (e.g. `"imports"`).
11
11
  - `timeout` (optional) — per-cell seconds. Raise only for heavy compute or long non-agent tool calls.
@@ -13,11 +13,13 @@ Cell fields:
13
13
 
14
14
  Work incrementally — one logical step per cell (imports, define, test, use), many small cells per call; workflow notes in the assistant message or `title`, never in cell code.
15
15
  {{#if py}}Live event loop: use top-level `await` directly; `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
16
+ {{#if rb}}Ruby: synchronous; helper options are keyword args (e.g. `tree(".", max_depth: 2)`); the last expression auto-displays unless it is `nil`, an assignment, or a definition (like IRB).{{/if}}
17
+ {{#if jl}}Julia: synchronous; helper options are standard keyword args (e.g. `tree(max_depth=2)`); the last expression auto-displays unless it is an assignment or a definition (like the Julia REPL).{{/if}}
16
18
  Errors name the failing cell ("Cell 3 failed") — resubmit the fixed cell + any remaining.
17
19
  </instruction>
18
20
 
19
21
  <prelude>
20
- {{#ifAll py js}}Same helpers + arg order, both runtimes. Python: sync, options = trailing kwargs. JS: async/`await`able, options = ONE trailing object literal, never positional (extras throw).{{else}}{{#if py}}Sync; options = trailing kwargs.{{/if}}{{#if js}}Async/`await`able; options = ONE trailing object literal, never positional (extras throw).{{/if}}{{/ifAll}}
22
+ {{#ifAll py js}}Same helpers + arg order, both runtimes. Python: sync, options = trailing kwargs. JS: async/`await`able, options = ONE trailing object literal, never positional (extras throw).{{else}}{{#if py}}Sync; options = trailing kwargs.{{/if}}{{#if js}}Async/`await`able; options = ONE trailing object literal, never positional (extras throw).{{/if}}{{/ifAll}}{{#if rb}} Ruby: sync, options = trailing keyword args.{{/if}}{{#if jl}} Julia: sync, options = trailing keyword args.{{/if}}
21
23
  ```
22
24
  display(value) → None
23
25
  Cell output; figures/images/dataframes shown natively.
@@ -55,13 +57,13 @@ log(message) → None
55
57
  phase(title) → None
56
58
  Phase grouping subsequent status lines.
57
59
  budget → per-turn token budget
58
- {{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard`.{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} Ceiling: `+Nk` (advisory) or `+Nk!`/Goal Mode (hard — `agent()` won't spawn past it); spend still tracked.
60
+ {{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard`.{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}}{{#if rb}} Ruby: `budget.total` (ceiling or nil), `budget.spent`, `budget.remaining` (Float::INFINITY when no ceiling), `budget.hard`.{{/if}}{{#if jl}} Julia: `budget.total` (ceiling or nothing), `budget.spent()`, `budget.remaining()` (Inf when no ceiling), `budget.hard`.{{/if}} Ceiling: `+Nk` (advisory) or `+Nk!`/Goal Mode (hard — `agent()` won't spawn past it); spend still tracked.
59
61
  ```
60
62
  </prelude>
61
63
  {{#if spawns}}
62
64
  <dag>
63
65
  Pipe handles through stage helpers to build a dependency graph — acyclic waves:
64
- - **Name nodes.** Capture each `agent(…, {{#if py}}return_handle=True{{/if}}{{#if js}}{ returnHandle: true }{{/if}})` result; carries `handle` (`agent://<id>`) + `output`.
66
+ - **Name nodes.** Capture each `agent(…, {{#if py}}return_handle=True{{/if}}{{#if js}}{ returnHandle: true }{{/if}}{{#if jl}}return_handle=true{{/if}})` result; carries `handle` (`agent://<id>`) + `output`.
65
67
  - **Wire edges by reference.** Put an upstream node's `handle`/`output` in the dependent stage's prompt — large transcript never re-inlined. Bulk: `write("local://<name>.md", …)`, pass the URI.
66
68
  - **`pipeline(items, *stages)` = staged waves**, barrier between stages (every item clears stage N before any enters N+1). **`parallel(thunks)` = one wave** of independent nodes.
67
69
  - **Isolate failure.** A raising node re-raises the lowest-index error, aborts its wave; wrap risky nodes in try/except so a failure degrades only its dependent subtree, independent branches finish.
@@ -13,7 +13,3 @@ Matching paths sorted by mtime (newest first), grouped under `# <dir>/` headers
13
13
  <avoid>
14
14
  Open-ended searches needing multiple rounds of globbing/searching: you MUST use the Task tool instead.
15
15
  </avoid>
16
-
17
- <critical>
18
- - You MUST use the built-in Find tool for every file-name lookup. NEVER shell out to `find`, `fd`, `locate`, `ls`, or `git ls-files` via Bash — they ignore `.gitignore`, blow past result limits, and waste tokens.
19
- </critical>
@@ -70,7 +70,6 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
70
70
  All URI schemes take the same line selectors. `artifact://<id>` recovers full output a bash/eval/tool result spilled or truncated. `history://<agentId>` = agent transcript; bare `history://` lists agents.
71
71
 
72
72
  <critical>
73
- - MUST use `read` for every file/directory/archive/URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN bash calls, however convenient.
74
- - Line ranges go in the selector (`path="src/foo.ts:50-200"`) — NEVER `sed -n`, `awk NR`, or `head`/`tail` pipelines.
73
+ - Line ranges go in the selector: `path="src/foo.ts:50-200"`.
75
74
  - Summary footer names elided ranges? Re-issue ONLY those ranges. NEVER guess `..`/`…` content.
76
75
  </critical>
@@ -25,5 +25,5 @@ For pattern-addressed bulk changes, bash is more efficient:
25
25
  |Bulk replace across files|`sd 'pattern' 'replacement' **/*.ts`|
26
26
 
27
27
  Use Replace when _content itself_ identifies location; use `ast_edit` for structure-aware codemods.
28
- NEVER use `sed -i`/`perl -i`/heredoc redirection for editsthose calls are blocked; use this tool or `write`.
28
+ For in-place edits prefer this tool or `write`you get a diff preview and fuzzy matching.
29
29
  </bash-alternatives>
@@ -17,6 +17,5 @@ Searches files using regex.
17
17
  </output>
18
18
 
19
19
  <critical>
20
- - MUST use built-in `search` for any content search. NEVER shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any CLI search via Bash — not even for one match or a quick check.
21
20
  - Open-ended search needing multiple rounds? MUST use the Task tool with the explore subagent, NOT chained `search` calls.
22
21
  </critical>
package/src/sdk.ts CHANGED
@@ -46,7 +46,9 @@ import { Settings, type SkillsSettings } from "./config/settings";
46
46
  import { CursorExecHandlers } from "./cursor";
47
47
  import "./discovery";
48
48
  import { initializeWithSettings } from "./discovery";
49
+ import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
49
50
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
51
+ import { disposeAllRubyKernelSessions, disposeRubyKernelSessionsByOwner } from "./eval/rb/executor";
50
52
  import { defaultEvalSessionId } from "./eval/session-id";
51
53
  import {
52
54
  type CustomCommandsLoadResult,
@@ -472,7 +474,7 @@ export interface CreateAgentSessionOptions {
472
474
 
473
475
  /** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
474
476
  enableLsp?: boolean;
475
- /** Skip Python kernel availability check and prelude warmup */
477
+ /** Skip subprocess-kernel availability checks and prelude warmup */
476
478
  skipPythonPreflight?: boolean;
477
479
  /** Tool names explicitly requested (enables disabled-by-default tools) */
478
480
  toolNames?: string[];
@@ -866,12 +868,14 @@ function registerSshCleanup(): void {
866
868
  postmortem.register("ssh-cleanup", cleanupSshResources);
867
869
  }
868
870
 
869
- let pythonCleanupRegistered = false;
871
+ let evalCleanupRegistered = false;
870
872
 
871
- function registerPythonCleanup(): void {
872
- if (pythonCleanupRegistered) return;
873
- pythonCleanupRegistered = true;
873
+ function registerEvalCleanup(): void {
874
+ if (evalCleanupRegistered) return;
875
+ evalCleanupRegistered = true;
874
876
  postmortem.register("python-cleanup", disposeAllKernelSessions);
877
+ postmortem.register("ruby-cleanup", disposeAllRubyKernelSessions);
878
+ postmortem.register("julia-cleanup", disposeAllJuliaKernelSessions);
875
879
  }
876
880
 
877
881
  function customToolToDefinition(tool: CustomTool): ToolDefinition {
@@ -1080,7 +1084,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1080
1084
  const eventBus = options.eventBus ?? new EventBus();
1081
1085
 
1082
1086
  registerSshCleanup();
1083
- registerPythonCleanup();
1087
+ registerEvalCleanup();
1084
1088
 
1085
1089
  // Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
1086
1090
  // failures through that instance, so any divergent storage handed to the bridge / mcpManager
@@ -2693,7 +2697,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2693
2697
  const originalDispose = session.dispose.bind(session);
2694
2698
  session.dispose = async () => {
2695
2699
  try {
2696
- // Reject new session work (Python/eval starts) the moment disposal
2700
+ // Reject new session work (eval starts) the moment disposal
2697
2701
  // begins — the lifecycle await below opens an async gap before
2698
2702
  // AgentSession.dispose() would otherwise set its guards.
2699
2703
  session.beginDispose();
@@ -2913,6 +2917,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2913
2917
  await asyncJobManager.dispose({ timeoutMs: 3_000 });
2914
2918
  }
2915
2919
  await disposeKernelSessionsByOwner(evalKernelOwnerId);
2920
+ await disposeRubyKernelSessionsByOwner(evalKernelOwnerId);
2921
+ await disposeJuliaKernelSessionsByOwner(evalKernelOwnerId);
2916
2922
  if (ownsAuthStorage) authStorage.close();
2917
2923
  }
2918
2924
  } catch (cleanupError) {
@@ -164,12 +164,14 @@ import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
164
164
  import { loadCapability } from "../discovery";
165
165
  import { expandApplyPatchToEntries, normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
166
166
  import { getFileSnapshotStore } from "../edit/file-snapshot-store";
167
+ import { disposeJuliaKernelSessionsByOwner } from "../eval/jl/executor";
167
168
  import { namespaceSessionId as namespacePythonSessionId } from "../eval/py";
168
169
  import {
169
170
  disposeKernelSessionsByOwner,
170
171
  executePython as executePythonCommand,
171
172
  type PythonResult,
172
173
  } from "../eval/py/executor";
174
+ import { disposeRubyKernelSessionsByOwner } from "../eval/rb/executor";
173
175
  import { defaultEvalSessionId } from "../eval/session-id";
174
176
  import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
175
177
  import type { TtsrManager, TtsrMatchContext } from "../export/ttsr";
@@ -520,7 +522,7 @@ export interface AgentSessionConfig {
520
522
  obfuscator?: SecretObfuscator;
521
523
  /** Inherited eval executor session id from a parent agent. */
522
524
  parentEvalSessionId?: string;
523
- /** Logical owner for retained Python kernels created by this session. */
525
+ /** Logical owner for retained eval kernels created by this session. */
524
526
  evalKernelOwnerId?: string;
525
527
  /**
526
528
  * AsyncJobManager that this session installed as the process-global instance.
@@ -4173,7 +4175,7 @@ export class AgentSession {
4173
4175
 
4174
4176
  /**
4175
4177
  * Synchronously mark the session as disposing so new work is rejected
4176
- * immediately: Python/eval starts throw, queued asides are dropped, and the
4178
+ * immediately: eval starts throw, queued asides are dropped, and the
4177
4179
  * aside provider is detached. Idempotent; `dispose()` runs it first.
4178
4180
  *
4179
4181
  * Wrappers that await other teardown before delegating to `dispose()` MUST
@@ -4235,13 +4237,13 @@ export class AgentSession {
4235
4237
  AsyncJobManager.setInstance(undefined);
4236
4238
  }
4237
4239
  }
4238
- const pythonExecutionsSettled = await this.#prepareEvalExecutionsForDispose();
4239
- if (!pythonExecutionsSettled) {
4240
- logger.warn(
4241
- "Detaching retained Python kernel ownership during dispose while Python execution is still active",
4242
- );
4240
+ const evalExecutionsSettled = await this.#prepareEvalExecutionsForDispose();
4241
+ if (!evalExecutionsSettled) {
4242
+ logger.warn("Detaching retained eval-kernel ownership during dispose while eval execution is still active");
4243
4243
  }
4244
4244
  await disposeKernelSessionsByOwner(this.#evalKernelOwnerId);
4245
+ await disposeRubyKernelSessionsByOwner(this.#evalKernelOwnerId);
4246
+ await disposeJuliaKernelSessionsByOwner(this.#evalKernelOwnerId);
4245
4247
  await shutdownTinyTitleClient();
4246
4248
  this.#releasePowerAssertion();
4247
4249
  await this.sessionManager.close();