@oh-my-pi/pi-coding-agent 14.5.12 → 14.5.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 (112) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +18 -10
  3. package/src/cli/jupyter-cli.ts +1 -1
  4. package/src/commit/pipeline.ts +4 -3
  5. package/src/config/model-equivalence.ts +49 -16
  6. package/src/config/model-registry.ts +100 -25
  7. package/src/config/model-resolver.ts +29 -15
  8. package/src/config/settings-schema.ts +20 -6
  9. package/src/config/settings.ts +9 -8
  10. package/src/config.ts +18 -6
  11. package/src/eval/backend.ts +43 -0
  12. package/src/eval/eval.lark +43 -0
  13. package/src/eval/index.ts +5 -0
  14. package/src/eval/js/context-manager.ts +717 -0
  15. package/src/eval/js/executor.ts +131 -0
  16. package/src/eval/js/index.ts +46 -0
  17. package/src/eval/js/prelude.ts +2 -0
  18. package/src/eval/js/prelude.txt +84 -0
  19. package/src/eval/js/tool-bridge.ts +124 -0
  20. package/src/eval/parse.ts +337 -0
  21. package/src/{ipy → eval/py}/executor.ts +2 -180
  22. package/src/{ipy → eval/py}/gateway-coordinator.ts +2 -2
  23. package/src/eval/py/index.ts +58 -0
  24. package/src/{ipy → eval/py}/kernel.ts +9 -45
  25. package/src/{ipy → eval/py}/prelude.py +39 -227
  26. package/src/eval/types.ts +48 -0
  27. package/src/export/html/template.generated.ts +1 -1
  28. package/src/export/html/template.js +8 -10
  29. package/src/extensibility/extensions/types.ts +2 -3
  30. package/src/internal-urls/docs-index.generated.ts +5 -5
  31. package/src/lsp/client.ts +9 -0
  32. package/src/lsp/index.ts +395 -0
  33. package/src/lsp/types.ts +15 -4
  34. package/src/main.ts +35 -14
  35. package/src/mcp/manager.ts +22 -0
  36. package/src/mcp/oauth-flow.ts +1 -1
  37. package/src/memories/index.ts +1 -1
  38. package/src/modes/acp/acp-event-mapper.ts +1 -1
  39. package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
  40. package/src/modes/components/login-dialog.ts +1 -1
  41. package/src/modes/components/oauth-selector.ts +2 -1
  42. package/src/modes/components/tool-execution.ts +3 -4
  43. package/src/modes/controllers/command-controller.ts +28 -8
  44. package/src/modes/controllers/input-controller.ts +4 -4
  45. package/src/modes/controllers/selector-controller.ts +2 -1
  46. package/src/modes/interactive-mode.ts +4 -5
  47. package/src/modes/rpc/rpc-client.ts +9 -0
  48. package/src/modes/rpc/rpc-mode.ts +6 -0
  49. package/src/modes/rpc/rpc-types.ts +9 -0
  50. package/src/modes/types.ts +3 -3
  51. package/src/modes/utils/ui-helpers.ts +2 -2
  52. package/src/prompts/system/system-prompt.md +3 -3
  53. package/src/prompts/tools/eval.md +92 -0
  54. package/src/prompts/tools/lsp.md +7 -3
  55. package/src/sdk.ts +64 -35
  56. package/src/session/agent-session.ts +152 -46
  57. package/src/session/messages.ts +1 -1
  58. package/src/slash-commands/builtin-registry.ts +1 -1
  59. package/src/system-prompt.ts +34 -66
  60. package/src/task/agents.ts +4 -5
  61. package/src/task/executor.ts +5 -9
  62. package/src/tools/archive-reader.ts +9 -3
  63. package/src/tools/browser/launch.ts +22 -0
  64. package/src/tools/browser/readable.ts +11 -6
  65. package/src/tools/browser/registry.ts +25 -244
  66. package/src/tools/browser/render.ts +1 -1
  67. package/src/tools/browser/tab-protocol.ts +101 -0
  68. package/src/tools/browser/tab-supervisor.ts +429 -0
  69. package/src/tools/browser/tab-worker-entry.ts +21 -0
  70. package/src/tools/browser/tab-worker.ts +1006 -0
  71. package/src/tools/browser.ts +17 -32
  72. package/src/tools/checkpoint.ts +2 -2
  73. package/src/tools/{python.ts → eval.ts} +324 -315
  74. package/src/tools/exit-plan-mode.ts +1 -1
  75. package/src/tools/image-gen.ts +2 -2
  76. package/src/tools/index.ts +62 -100
  77. package/src/tools/read.ts +0 -6
  78. package/src/tools/recipe/runners/pkg.ts +34 -32
  79. package/src/tools/renderers.ts +2 -2
  80. package/src/tools/resolve.ts +7 -2
  81. package/src/tools/todo-write.ts +0 -1
  82. package/src/tools/tool-timeouts.ts +2 -2
  83. package/src/tools/write.ts +8 -1
  84. package/src/utils/markit.ts +15 -7
  85. package/src/utils/tools-manager.ts +5 -5
  86. package/src/web/scrapers/crossref.ts +3 -3
  87. package/src/web/scrapers/devto.ts +1 -1
  88. package/src/web/scrapers/discourse.ts +5 -5
  89. package/src/web/scrapers/firefox-addons.ts +1 -1
  90. package/src/web/scrapers/flathub.ts +2 -2
  91. package/src/web/scrapers/gitlab.ts +1 -1
  92. package/src/web/scrapers/go-pkg.ts +2 -2
  93. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  94. package/src/web/scrapers/mastodon.ts +9 -9
  95. package/src/web/scrapers/mdn.ts +11 -7
  96. package/src/web/scrapers/pub-dev.ts +1 -1
  97. package/src/web/scrapers/rawg.ts +3 -3
  98. package/src/web/scrapers/readthedocs.ts +1 -1
  99. package/src/web/scrapers/spdx.ts +1 -1
  100. package/src/web/scrapers/stackoverflow.ts +2 -2
  101. package/src/web/scrapers/types.ts +53 -39
  102. package/src/web/scrapers/w3c.ts +1 -1
  103. package/src/web/search/index.ts +5 -5
  104. package/src/web/search/provider.ts +121 -39
  105. package/src/web/search/providers/gemini.ts +4 -4
  106. package/src/web/search/render.ts +2 -2
  107. package/src/ipy/modules.ts +0 -144
  108. package/src/prompts/tools/python.md +0 -57
  109. package/src/tools/browser/vm.ts +0 -792
  110. /package/src/{ipy → eval/py}/cancellation.ts +0 -0
  111. /package/src/{ipy → eval/py}/prelude.ts +0 -0
  112. /package/src/{ipy → eval/py}/runtime.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Component for displaying user-initiated Python execution with streaming output.
3
- * Shares the same kernel session as the agent's Python tool.
2
+ * Component for displaying user-initiated eval execution with streaming output.
3
+ * Shares the same kernel session as the agent's eval tool.
4
4
  */
5
5
 
6
6
  import { sanitizeText } from "@oh-my-pi/pi-natives";
@@ -13,7 +13,9 @@ import { truncateToVisualLines } from "./visual-truncate";
13
13
  const PREVIEW_LINES = 20;
14
14
  const MAX_DISPLAY_LINE_CHARS = 4000;
15
15
 
16
- export class PythonExecutionComponent extends Container {
16
+ export type EvalExecutionLanguage = "python" | "js";
17
+
18
+ export class EvalExecutionComponent extends Container {
17
19
  #outputLines: string[] = [];
18
20
  #status: "running" | "complete" | "cancelled" | "error" = "running";
19
21
  #exitCode: number | undefined = undefined;
@@ -22,10 +24,14 @@ export class PythonExecutionComponent extends Container {
22
24
  #expanded = false;
23
25
  #contentContainer: Container;
24
26
 
27
+ #highlightLang(): "python" | "javascript" {
28
+ return this.language === "js" ? "javascript" : "python";
29
+ }
30
+
25
31
  #formatHeader(colorKey: "dim" | "pythonMode"): Text {
26
32
  const prompt = theme.fg(colorKey, theme.bold(">>>"));
27
33
  const continuation = theme.fg(colorKey, " ");
28
- const codeLines = highlightCode(this.code, "python");
34
+ const codeLines = highlightCode(this.code, this.#highlightLang());
29
35
  const headerLines = codeLines.map((line, index) =>
30
36
  index === 0 ? `${prompt} ${line}` : `${continuation}${line}`,
31
37
  );
@@ -36,6 +42,7 @@ export class PythonExecutionComponent extends Container {
36
42
  private readonly code: string,
37
43
  ui: TUI,
38
44
  private readonly excludeFromContext = false,
45
+ private readonly language: EvalExecutionLanguage = "python",
39
46
  ) {
40
47
  super();
41
48
 
@@ -1,4 +1,4 @@
1
- import { getOAuthProviders } from "@oh-my-pi/pi-ai";
1
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
2
2
  import { Container, getKeybindings, Input, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
3
3
  import { theme } from "../../modes/theme/theme";
4
4
  import { openPath } from "../../utils/open";
@@ -1,4 +1,5 @@
1
- import { getOAuthProviders, type OAuthProviderInfo } from "@oh-my-pi/pi-ai";
1
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
2
+ import type { OAuthProviderInfo } from "@oh-my-pi/pi-ai/utils/oauth/types";
2
3
  import { Container, matchesKey, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
3
4
  import { theme } from "../../modes/theme/theme";
4
5
  import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
@@ -18,6 +18,7 @@ import { EDIT_MODE_STRATEGIES, type EditMode, type PerFileDiffPreview } from "..
18
18
  import type { Theme } from "../../modes/theme/theme";
19
19
  import { theme } from "../../modes/theme/theme";
20
20
  import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
21
+ import { EVAL_DEFAULT_PREVIEW_LINES } from "../../tools/eval";
21
22
  import {
22
23
  formatArgsInline,
23
24
  JSON_TREE_MAX_DEPTH_COLLAPSED,
@@ -28,7 +29,6 @@ import {
28
29
  JSON_TREE_SCALAR_LEN_EXPANDED,
29
30
  renderJsonTreeLines,
30
31
  } from "../../tools/json-tree";
31
- import { PYTHON_DEFAULT_PREVIEW_LINES } from "../../tools/python";
32
32
  import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
33
33
  import { toolRenderers } from "../../tools/renderers";
34
34
  import { renderStatusLine } from "../../tui";
@@ -668,12 +668,11 @@ export class ToolExecutionComponent extends Container {
668
668
  context.expanded = this.#expanded;
669
669
  context.previewLines = BASH_DEFAULT_PREVIEW_LINES;
670
670
  context.timeout = normalizeTimeoutSeconds(this.#args?.timeout, 3600);
671
- } else if (this.#toolName === "python" && this.#result) {
671
+ } else if (this.#toolName === "eval" && this.#result) {
672
672
  const output = this.#getTextOutput().trimEnd();
673
673
  context.output = output;
674
674
  context.expanded = this.#expanded;
675
- context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
676
- context.timeout = normalizeTimeoutSeconds(this.#args?.timeout, 600);
675
+ context.previewLines = EVAL_DEFAULT_PREVIEW_LINES;
677
676
  } else if (isEditLikeToolName(this.#toolName)) {
678
677
  context.editMode = this.#editMode;
679
678
  const previews = this.#editDiffPreview;
@@ -14,14 +14,14 @@ import { formatDuration, Snowflake, setProjectDir } from "@oh-my-pi/pi-utils";
14
14
  import { $ } from "bun";
15
15
  import { reset as resetCapabilities } from "../../capability";
16
16
  import { clearClaudePluginRootsCache } from "../../discovery/helpers";
17
+ import { getGatewayStatus } from "../../eval/py/gateway-coordinator";
17
18
  import { loadCustomShare } from "../../export/custom-share";
18
19
  import type { CompactOptions } from "../../extensibility/extensions/types";
19
- import { getGatewayStatus } from "../../ipy/gateway-coordinator";
20
20
  import { buildMemoryToolDeveloperInstructions, clearMemoryData, enqueueMemoryConsolidation } from "../../memories";
21
21
  import { BashExecutionComponent } from "../../modes/components/bash-execution";
22
22
  import { BorderedLoader } from "../../modes/components/bordered-loader";
23
23
  import { DynamicBorder } from "../../modes/components/dynamic-border";
24
- import { PythonExecutionComponent } from "../../modes/components/python-execution";
24
+ import { EvalExecutionComponent } from "../../modes/components/eval-execution";
25
25
  import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
26
26
  import type { InteractiveModeContext } from "../../modes/types";
27
27
  import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
@@ -285,9 +285,26 @@ export class CommandController {
285
285
  this.#doCopy(combined, `Copied ${matches.length} code block${matches.length > 1 ? "s" : ""} to clipboard`);
286
286
  }
287
287
 
288
+ #extractEvalCode(args: unknown): string | undefined {
289
+ if (!args || typeof args !== "object") return undefined;
290
+ const cells = (args as { cells?: unknown }).cells;
291
+ if (!Array.isArray(cells)) return undefined;
292
+
293
+ const codeBlocks: string[] = [];
294
+ for (const cell of cells) {
295
+ if (!cell || typeof cell !== "object") continue;
296
+ const code = (cell as { code?: unknown }).code;
297
+ if (typeof code === "string" && code.length > 0) {
298
+ codeBlocks.push(code);
299
+ }
300
+ }
301
+
302
+ return codeBlocks.length > 0 ? codeBlocks.join("\n\n") : undefined;
303
+ }
304
+
288
305
  #copyLastCommand() {
289
306
  const messages = this.ctx.session.messages;
290
- // Walk backwards to find the last bash/python tool call
307
+ // Walk backwards to find the last bash/eval tool call
291
308
  for (let i = messages.length - 1; i >= 0; i--) {
292
309
  const msg = messages[i];
293
310
  if (msg.role !== "assistant") continue;
@@ -298,13 +315,16 @@ export class CommandController {
298
315
  this.#doCopy(tc.arguments.command, "Copied last bash command to clipboard");
299
316
  return;
300
317
  }
301
- if (tc.name === "python" && typeof tc.arguments.code === "string") {
302
- this.#doCopy(tc.arguments.code, "Copied last python code to clipboard");
303
- return;
318
+ if (tc.name === "eval") {
319
+ const code = this.#extractEvalCode(tc.arguments);
320
+ if (code) {
321
+ this.#doCopy(code, "Copied last eval code to clipboard");
322
+ return;
323
+ }
304
324
  }
305
325
  }
306
326
  }
307
- this.ctx.showWarning("No bash or python command found in the conversation.");
327
+ this.ctx.showWarning("No bash or eval command found in the conversation.");
308
328
  }
309
329
 
310
330
  #doCopy(content: string, label: string) {
@@ -779,7 +799,7 @@ export class CommandController {
779
799
 
780
800
  async handlePythonCommand(code: string, excludeFromContext = false): Promise<void> {
781
801
  const isDeferred = this.ctx.session.isStreaming;
782
- this.ctx.pythonComponent = new PythonExecutionComponent(code, this.ctx.ui, excludeFromContext);
802
+ this.ctx.pythonComponent = new EvalExecutionComponent(code, this.ctx.ui, excludeFromContext);
783
803
 
784
804
  if (isDeferred) {
785
805
  this.ctx.pendingMessagesContainer.addChild(this.ctx.pythonComponent);
@@ -37,7 +37,7 @@ export class InputController {
37
37
  this.ctx.session.isCompacting ||
38
38
  this.ctx.session.isGeneratingHandoff ||
39
39
  this.ctx.session.isBashRunning ||
40
- this.ctx.session.isPythonRunning ||
40
+ this.ctx.session.isEvalRunning ||
41
41
  this.ctx.autoCompactionLoader ||
42
42
  this.ctx.retryLoader ||
43
43
  this.ctx.autoCompactionEscapeHandler ||
@@ -67,8 +67,8 @@ export class InputController {
67
67
  this.ctx.editor.setText("");
68
68
  this.ctx.isBashMode = false;
69
69
  this.ctx.updateEditorBorderColor();
70
- } else if (this.ctx.session.isPythonRunning) {
71
- this.ctx.session.abortPython();
70
+ } else if (this.ctx.session.isEvalRunning) {
71
+ this.ctx.session.abortEval();
72
72
  } else if (this.ctx.isPythonMode) {
73
73
  this.ctx.editor.setText("");
74
74
  this.ctx.isPythonMode = false;
@@ -304,7 +304,7 @@ export class InputController {
304
304
  const isExcluded = text.startsWith("$$");
305
305
  const code = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
306
306
  if (code) {
307
- if (this.ctx.session.isPythonRunning) {
307
+ if (this.ctx.session.isEvalRunning) {
308
308
  this.ctx.showWarning("A Python execution is already running. Press Esc to cancel it first.");
309
309
  this.ctx.editor.setText(text);
310
310
  return;
@@ -1,7 +1,8 @@
1
1
  import * as os from "node:os";
2
2
  import * as path from "node:path";
3
3
  import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
- import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
4
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
5
+ import type { OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types";
5
6
  import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
6
7
  import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
7
8
  import { getAgentDbPath, getConfigDirName, getProjectDir } from "@oh-my-pi/pi-utils";
@@ -57,10 +57,10 @@ import type { AssistantMessageComponent } from "./components/assistant-message";
57
57
  import type { BashExecutionComponent } from "./components/bash-execution";
58
58
  import { CustomEditor } from "./components/custom-editor";
59
59
  import { DynamicBorder } from "./components/dynamic-border";
60
+ import type { EvalExecutionComponent } from "./components/eval-execution";
60
61
  import type { HookEditorComponent } from "./components/hook-editor";
61
62
  import type { HookInputComponent } from "./components/hook-input";
62
63
  import type { HookSelectorComponent } from "./components/hook-selector";
63
- import type { PythonExecutionComponent } from "./components/python-execution";
64
64
  import { StatusLineComponent } from "./components/status-line";
65
65
  import type { ToolExecutionHandle } from "./components/tool-execution";
66
66
  import { WelcomeComponent, type LspServerInfo as WelcomeLspServerInfo } from "./components/welcome";
@@ -166,8 +166,8 @@ export class InteractiveMode implements InteractiveModeContext {
166
166
  pendingTools = new Map<string, ToolExecutionHandle>();
167
167
  pendingBashComponents: BashExecutionComponent[] = [];
168
168
  bashComponent: BashExecutionComponent | undefined = undefined;
169
- pendingPythonComponents: PythonExecutionComponent[] = [];
170
- pythonComponent: PythonExecutionComponent | undefined = undefined;
169
+ pendingPythonComponents: EvalExecutionComponent[] = [];
170
+ pythonComponent: EvalExecutionComponent | undefined = undefined;
171
171
  isPythonMode = false;
172
172
  streamingComponent: AssistantMessageComponent | undefined = undefined;
173
173
  streamingMessage: AssistantMessage | undefined = undefined;
@@ -335,8 +335,7 @@ export class InteractiveMode implements InteractiveModeContext {
335
335
  async init(): Promise<void> {
336
336
  if (this.isInitialized) return;
337
337
 
338
- logger.time("InteractiveMode.init:keybindings");
339
- this.keybindings = KeybindingsManager.create();
338
+ this.keybindings = logger.time("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
340
339
 
341
340
  // Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
342
341
  this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
@@ -11,6 +11,7 @@ import type { SessionStats } from "../../session/agent-session";
11
11
  import type { CompactionResult } from "../../session/compaction";
12
12
  import type {
13
13
  RpcCommand,
14
+ RpcHandoffResult,
14
15
  RpcHostToolCallRequest,
15
16
  RpcHostToolCancelRequest,
16
17
  RpcHostToolDefinition,
@@ -457,6 +458,14 @@ export class RpcClient {
457
458
  return this.#getData(response);
458
459
  }
459
460
 
461
+ /**
462
+ * Hand off session context to a new session.
463
+ */
464
+ async handoff(customInstructions?: string): Promise<RpcHandoffResult | null> {
465
+ const response = await this.#send({ type: "handoff", customInstructions });
466
+ return this.#getData(response);
467
+ }
468
+
460
469
  /**
461
470
  * Export session to HTML.
462
471
  */
@@ -574,6 +574,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
574
574
  description: tool.description,
575
575
  parameters: tool.parameters,
576
576
  })),
577
+ contextUsage: session.getContextUsage(),
577
578
  };
578
579
  return success(id, "get_state", state);
579
580
  }
@@ -741,6 +742,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
741
742
  return success(id, "set_session_name");
742
743
  }
743
744
 
745
+ case "handoff": {
746
+ const result = await session.handoff(command.customInstructions);
747
+ return success(id, "handoff", result ? { savedPath: result.savedPath } : null);
748
+ }
749
+
744
750
  // =================================================================
745
751
  // Messages
746
752
  // =================================================================
@@ -7,6 +7,7 @@
7
7
  import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
8
  import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
9
9
  import type { BashResult } from "../../exec/bash-executor";
10
+ import type { ContextUsage } from "../../extensibility/extensions/types";
10
11
  import type { SessionStats } from "../../session/agent-session";
11
12
  import type { CompactionResult } from "../../session/compaction";
12
13
  import type { TodoPhase } from "../../tools/todo-write";
@@ -63,6 +64,7 @@ export type RpcCommand =
63
64
  | { id?: string; type: "get_branch_messages" }
64
65
  | { id?: string; type: "get_last_assistant_text" }
65
66
  | { id?: string; type: "set_session_name"; name: string }
67
+ | { id?: string; type: "handoff"; customInstructions?: string }
66
68
 
67
69
  // Messages
68
70
  | { id?: string; type: "get_messages" };
@@ -89,6 +91,12 @@ export interface RpcSessionState {
89
91
  /** For session dump / export (plain-text parity with /dump). */
90
92
  systemPrompt?: string;
91
93
  dumpTools?: Array<{ name: string; description: string; parameters: unknown }>;
94
+ /** Current context window usage. Null tokens/percent when unknown (e.g. right after compaction). */
95
+ contextUsage?: ContextUsage;
96
+ }
97
+
98
+ export interface RpcHandoffResult {
99
+ savedPath?: string;
92
100
  }
93
101
 
94
102
  // ============================================================================
@@ -180,6 +188,7 @@ export type RpcResponse =
180
188
  data: { text: string | null };
181
189
  }
182
190
  | { id?: string; type: "response"; command: "set_session_name"; success: true }
191
+ | { id?: string; type: "response"; command: "handoff"; success: true; data: RpcHandoffResult | null }
183
192
 
184
193
  // Messages
185
194
  | { id?: string; type: "response"; command: "get_messages"; success: true; data: { messages: AgentMessage[] } }
@@ -18,10 +18,10 @@ import type { ExitPlanModeDetails, LspStartupServerInfo } from "../tools";
18
18
  import type { AssistantMessageComponent } from "./components/assistant-message";
19
19
  import type { BashExecutionComponent } from "./components/bash-execution";
20
20
  import type { CustomEditor } from "./components/custom-editor";
21
+ import type { EvalExecutionComponent } from "./components/eval-execution";
21
22
  import type { HookEditorComponent } from "./components/hook-editor";
22
23
  import type { HookInputComponent } from "./components/hook-input";
23
24
  import type { HookSelectorComponent } from "./components/hook-selector";
24
- import type { PythonExecutionComponent } from "./components/python-execution";
25
25
  import type { StatusLineComponent } from "./components/status-line";
26
26
  import type { ToolExecutionHandle } from "./components/tool-execution";
27
27
  import type { OAuthManualInputManager } from "./oauth-manual-input";
@@ -93,8 +93,8 @@ export interface InteractiveModeContext {
93
93
  pendingTools: Map<string, ToolExecutionHandle>;
94
94
  pendingBashComponents: BashExecutionComponent[];
95
95
  bashComponent: BashExecutionComponent | undefined;
96
- pendingPythonComponents: PythonExecutionComponent[];
97
- pythonComponent: PythonExecutionComponent | undefined;
96
+ pendingPythonComponents: EvalExecutionComponent[];
97
+ pythonComponent: EvalExecutionComponent | undefined;
98
98
  isPythonMode: boolean;
99
99
  streamingComponent: AssistantMessageComponent | undefined;
100
100
  streamingMessage: AssistantMessage | undefined;
@@ -8,7 +8,7 @@ import { BranchSummaryMessageComponent } from "../../modes/components/branch-sum
8
8
  import { CompactionSummaryMessageComponent } from "../../modes/components/compaction-summary-message";
9
9
  import { CustomMessageComponent } from "../../modes/components/custom-message";
10
10
  import { DynamicBorder } from "../../modes/components/dynamic-border";
11
- import { PythonExecutionComponent } from "../../modes/components/python-execution";
11
+ import { EvalExecutionComponent } from "../../modes/components/eval-execution";
12
12
  import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
13
13
  import { SkillMessageComponent } from "../../modes/components/skill-message";
14
14
  import { ToolExecutionComponent } from "../../modes/components/tool-execution";
@@ -84,7 +84,7 @@ export class UiHelpers {
84
84
  break;
85
85
  }
86
86
  case "pythonExecution": {
87
- const component = new PythonExecutionComponent(message.code, this.ctx.ui, message.excludeFromContext);
87
+ const component = new EvalExecutionComponent(message.code, this.ctx.ui, message.excludeFromContext);
88
88
  if (message.output) {
89
89
  component.appendOutput(message.output);
90
90
  }
@@ -216,12 +216,12 @@ Most tools have a `{{intentField}}` parameter. Fill it with a concise intent in
216
216
  If the task may involve external systems, SaaS APIs, chat, tickets, databases, deployments, or other non-local integrations, you **SHOULD** call `{{toolRefs.search_tool_bm25}}` before concluding no such tool exists.
217
217
  {{/if}}
218
218
 
219
- {{#ifAny (includes tools "python") (includes tools "bash")}}
219
+ {{#ifAny (includes tools "eval") (includes tools "bash")}}
220
220
  ### Tool priority
221
221
  1. Use specialized tools first{{#ifAny (includes tools "read") (includes tools "search") (includes tools "find") (includes tools "edit") (includes tools "lsp")}}: {{#has tools "read"}}`{{toolRefs.read}}`, {{/has}}{{#has tools "search"}}`{{toolRefs.search}}`, {{/has}}{{#has tools "find"}}`{{toolRefs.find}}`, {{/has}}{{#has tools "edit"}}`{{toolRefs.edit}}`, {{/has}}{{#has tools "lsp"}}`{{toolRefs.lsp}}`{{/has}}{{/ifAny}}
222
- 2. Python: logic, loops, processing, display
222
+ 2. Eval: logic, loops, processing, display (default python; pass `language: "js"` for in-process JavaScript)
223
223
  3. Bash: simple one-liners only
224
- You **MUST NOT** use Python or Bash when a specialized tool exists.
224
+ You **MUST NOT** use Eval or Bash when a specialized tool exists.
225
225
  {{/ifAny}}
226
226
 
227
227
  {{#ifAny (includes tools "read") (includes tools "write") (includes tools "search") (includes tools "find") (includes tools "edit")}}
@@ -0,0 +1,92 @@
1
+ Run code in a persistent kernel, using a series of codeblocks acting as cells.
2
+
3
+ <instruction>
4
+ Each cell is a markdown fenced code block. The opening fence's info string carries metadata:
5
+
6
+ ```
7
+ <lang>? <duration>? (title-fragment | key=value)*
8
+ ```
9
+ - **Language**: {{#if py}}`py`/`python` for Python{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`js`/`javascript`/`ts`/`typescript` for JavaScript{{/if}}.{{#ifAll py js}} Omitted → inherit the previous cell's language (the first cell defaults to Python, falling back to JavaScript when Python is unavailable).{{else}} Omitted → inherit the previous cell's language.{{/ifAll}}
10
+ - **Positional duration**: `15s`, `500ms`, `2m`, or a bare integer (seconds). Default 30s.
11
+ - **Attributes**:
12
+ - `id="…"` — cell id (shown as the title in the transcript).
13
+ - `t=<duration>` — overrides the positional duration.
14
+ - `rst=true` — wipe **this cell's own language kernel** before running.{{#ifAll py js}} Other languages are untouched.{{/ifAll}}
15
+
16
+ **Work incrementally:** one logical step per cell (imports, define, test, use). Pass multiple small cells in one call. Define small reusable functions you can debug individually. You **MUST** put workflow explanations in the assistant message or cell title — never inside cell code.
17
+
18
+ **On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
19
+ </instruction>
20
+
21
+ <prelude>
22
+ {{#ifAll py js}}The same helpers are available in both runtimes with the same positional argument order. Python takes the trailing options as keyword args; JavaScript takes the same options as a trailing object literal. JavaScript helpers are async and `await`able; Python helpers run synchronously.{{else}}{{#if py}}Helpers run synchronously. Trailing options are passed as keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are passed as a final object literal.{{/if}}{{/ifAll}}
23
+ ```
24
+ display(value) → None
25
+ Render a value in the current cell output.
26
+ print(value, ...) → None
27
+ Print to the cell's text output.
28
+ read(path, offset?=1, limit?=None) → str
29
+ Read file contents as text. offset/limit are 1-indexed line bounds.
30
+ write(path, content) → str
31
+ Write content to a file (creates parent directories). Returns the resolved path.
32
+ append(path, content) → str
33
+ Append content to a file. Returns the resolved path.
34
+ stat(path) → {path, size, is_file, is_dir, mtime}
35
+ File or directory metadata. mtime is an ISO-8601 string.
36
+ find(pattern, path?=".", type?="file", limit?=1000, hidden?=False, sort_by_mtime?=False, maxdepth?=None, mindepth?=None) → list[path]
37
+ Recursive glob find. Respects .gitignore.
38
+ glob(pattern, path?=".", hidden?=False) → list[path]
39
+ Non-recursive glob. Use find() for recursive walks. Respects .gitignore.
40
+ grep(pattern, path, ignore_case?=False, literal?=False, context?=0) → list[{line, text}]
41
+ Search a single file.
42
+ rgrep(pattern, path?=".", glob_pattern?="*", ignore_case?=False, literal?=False, limit?=100, hidden?=False) → list[{file, line, text}]
43
+ Search recursively across files. Respects .gitignore.
44
+ sed(path, pattern, repl, flags?=0) → int
45
+ Regex replace in a file (like sed -i). Returns replacement count.
46
+ tree(path?=".", max_depth?=3, show_hidden?=False) → str
47
+ Render a directory tree.
48
+ diff(a, b) → str
49
+ Unified diff between two files.
50
+ run(cmd, cwd?=None, timeout?=None) → {stdout, stderr, exit_code}
51
+ Run a shell command.
52
+ env(key?=None, value?=None) → str | None | dict
53
+ No args → full environment as dict. One arg → value of `key`. Two args → set `key=value` and return value.
54
+ output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict]
55
+ Read task/agent output by ID. Single id returns text/dict; multiple ids return a list.
56
+ ```
57
+
58
+ {{#if js}}**JavaScript only:** `tool.<name>(args)` invokes any session tool directly (e.g. `await tool.read({ path: "src/foo.ts" })`).
59
+ {{/if}}</prelude>
60
+
61
+ <output>
62
+ Cells render like a Jupyter notebook. Pass any value to `display(value)`; non-presentable data is rendered as an interactive JSON tree, and presentable values (figures, images, dataframes, etc.) render with their native representation.
63
+ </output>
64
+
65
+ <caution>
66
+ - In session mode, use `rst=true` on a cell to wipe its language's kernel before running.{{#ifAll py js}} Reset is per-language: a python cell's `rst=true` does not touch the JavaScript kernel and vice versa.{{/ifAll}}
67
+ {{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`.
68
+ {{/if}}</caution>
69
+
70
+ <example>
71
+ {{#if py}}```py id="imports" t="10s"
72
+ import json
73
+ from pathlib import Path
74
+ ```
75
+
76
+ ```py id="load config"
77
+ data = json.loads(read('package.json'))
78
+ display(data)
79
+ ```
80
+ {{/if}}{{#ifAll py js}}
81
+
82
+ {{/ifAll}}{{#if js}}```js id="js summary" rst=true
83
+ const data = JSON.parse(await read('package.json'));
84
+ display(data);
85
+ return data.name;
86
+ ```
87
+
88
+ ```
89
+ return 'still JavaScript';
90
+ ```
91
+ {{/if}}
92
+ </example>
@@ -9,8 +9,11 @@ Interacts with Language Server Protocol servers for code intelligence.
9
9
  - `hover`: Get type info and documentation → type signature + docs
10
10
  - `symbols`: List symbols in a file, or search workspace with `file: "*"` and a `query`
11
11
  - `rename`: Rename symbol across codebase → preview or apply edits
12
+ - `rename_file`: Rename or move a file/directory; sends `workspace/willRenameFiles` so LSP servers update import paths and other references → preview or apply edits + filesystem rename
12
13
  - `code_actions`: List available quick-fixes/refactors/import actions; apply one when `apply: true` and `query` matches title or index
13
14
  - `status`: Show active language servers
15
+ - `capabilities`: Dump per-server capabilities (standard + experimental + executeCommand list) for discovery — file scopes to one server, omitted/`"*"` lists every active server
16
+ - `request`: Send a raw LSP request to a server — `query` is the method name (e.g., `rust-analyzer/expandMacro`, `typescript/goToSourceDefinition`, `workspace/executeCommand`); use `payload` for arbitrary JSON params or let the tool auto-build them from `file`/`line`/`symbol`
14
17
  - `reload`: Restart a specific server (via `file`) or all servers with `file: "*"`
15
18
  </operations>
16
19
 
@@ -18,9 +21,10 @@ Interacts with Language Server Protocol servers for code intelligence.
18
21
  - `file`: File path, glob pattern (e.g. `src/**/*.ts`), or `"*"` for workspace scope. Globs are expanded locally before dispatch. `"*"` routes `diagnostics`/`symbols`/`reload` to their workspace-wide form.
19
22
  - `line`: 1-indexed line number for position-based actions
20
23
  - `symbol`: Substring on the target line used to resolve column automatically. Append `#N` to pick the Nth occurrence on that line (1-indexed; default 1) — e.g. `foo#2` selects the second `foo`.
21
- - `query`: Symbol search query, code-action kind filter (list mode), or code-action selector (apply mode)
22
- - `new_name`: Required for rename
23
- - `apply`: Apply edits for rename/code_actions (default true for rename, list mode for code_actions unless explicitly true)
24
+ - `query`: Symbol search query, code-action kind filter / selector (list/apply mode), or LSP method name when `action: request`
25
+ - `new_name`: Required for `rename` (new symbol identifier) and `rename_file` (destination path)
26
+ - `apply`: Apply edits for rename/rename_file/code_actions (default true for rename and rename_file; list mode for code_actions unless explicitly true)
27
+ - `payload`: JSON-encoded params for `action: request`. Overrides the auto-built `{ textDocument, position }` shape when present.
24
28
  - `timeout`: Request timeout in seconds (clamped to 5-60, default 20)
25
29
  </parameters>
26
30