@oh-my-pi/pi-coding-agent 14.5.11 → 14.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/package.json +18 -10
- package/src/cli/jupyter-cli.ts +1 -1
- package/src/config/model-equivalence.ts +49 -16
- package/src/config/model-registry.ts +100 -25
- package/src/config/model-resolver.ts +29 -15
- package/src/config/settings-schema.ts +20 -6
- package/src/config/settings.ts +9 -8
- package/src/config.ts +9 -0
- package/src/eval/backend.ts +43 -0
- package/src/eval/eval.lark +43 -0
- package/src/eval/index.ts +5 -0
- package/src/eval/js/context-manager.ts +717 -0
- package/src/eval/js/executor.ts +131 -0
- package/src/eval/js/index.ts +46 -0
- package/src/eval/js/prelude.ts +2 -0
- package/src/eval/js/prelude.txt +84 -0
- package/src/eval/js/tool-bridge.ts +124 -0
- package/src/eval/parse.ts +337 -0
- package/src/{ipy → eval/py}/executor.ts +2 -180
- package/src/{ipy → eval/py}/gateway-coordinator.ts +4 -3
- package/src/eval/py/index.ts +58 -0
- package/src/{ipy → eval/py}/kernel.ts +5 -41
- package/src/{ipy → eval/py}/prelude.py +39 -227
- package/src/eval/types.ts +48 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +23 -17
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +9 -0
- package/src/lsp/index.ts +395 -0
- package/src/lsp/types.ts +15 -4
- package/src/main.ts +25 -14
- package/src/mcp/oauth-flow.ts +1 -1
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +2 -1
- package/src/modes/components/tool-execution.ts +3 -4
- package/src/modes/controllers/command-controller.ts +28 -8
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +2 -1
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/types.ts +3 -3
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/prompts/system/system-prompt.md +3 -3
- package/src/prompts/tools/atom.md +3 -2
- package/src/prompts/tools/browser.md +61 -16
- package/src/prompts/tools/eval.md +92 -0
- package/src/prompts/tools/lsp.md +7 -3
- package/src/sdk.ts +45 -31
- package/src/session/agent-session.ts +44 -54
- package/src/session/messages.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/system-prompt.ts +34 -66
- package/src/task/executor.ts +5 -9
- package/src/tools/browser/attach.ts +175 -0
- package/src/tools/browser/launch.ts +576 -0
- package/src/tools/browser/readable.ts +90 -0
- package/src/tools/browser/registry.ts +198 -0
- package/src/tools/browser/render.ts +212 -0
- package/src/tools/browser/tab-protocol.ts +101 -0
- package/src/tools/browser/tab-supervisor.ts +429 -0
- package/src/tools/browser/tab-worker-entry.ts +21 -0
- package/src/tools/browser/tab-worker.ts +1006 -0
- package/src/tools/browser.ts +231 -1567
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/{python.ts → eval.ts} +324 -315
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/index.ts +62 -100
- package/src/tools/plan-mode-guard.ts +27 -1
- package/src/tools/read.ts +0 -6
- package/src/tools/recipe/runners/pkg.ts +34 -32
- package/src/tools/renderers.ts +4 -2
- package/src/tools/resolve.ts +7 -2
- package/src/tools/todo-write.ts +0 -1
- package/src/tools/tool-timeouts.ts +2 -2
- package/src/utils/markit.ts +15 -7
- package/src/utils/tools-manager.ts +5 -5
- package/src/web/search/index.ts +5 -5
- package/src/web/search/provider.ts +121 -39
- package/src/web/search/providers/gemini.ts +2 -2
- package/src/web/search/render.ts +2 -2
- package/src/ipy/modules.ts +0 -144
- package/src/prompts/tools/python.md +0 -57
- /package/src/{ipy → eval/py}/cancellation.ts +0 -0
- /package/src/{ipy → eval/py}/prelude.ts +0 -0
- /package/src/{ipy → eval/py}/runtime.ts +0 -0
|
@@ -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 {
|
|
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/
|
|
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 === "
|
|
302
|
-
this.#
|
|
303
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
71
|
-
this.ctx.session.
|
|
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.
|
|
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
|
|
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:
|
|
170
|
-
pythonComponent:
|
|
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());
|
package/src/modes/types.ts
CHANGED
|
@@ -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:
|
|
97
|
-
pythonComponent:
|
|
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 {
|
|
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
|
|
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 "
|
|
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.
|
|
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
|
|
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")}}
|
|
@@ -36,7 +36,7 @@ Lid= blank the anchored line's content but KEEP the line (results in an em
|
|
|
36
36
|
- To insert ABOVE a line, you **MUST** use `^Lid` then `+TEXT`. To insert above line 1, you **MUST** use `^` (BOF) then `+TEXT`. To insert below a line, you **MUST** use `@Lid` then `+TEXT`.
|
|
37
37
|
- Multiple `---PATH` sections **MAY** appear in one input; each section is applied in order.
|
|
38
38
|
- `!rm` / `!mv DEST` **MUST NOT** be combined with line edits in the same section.
|
|
39
|
-
- Lids contain a content hash. If a line has changed since you read it, the tool rejects the edit and shows the current content; you **MUST** re-read and retry with fresh Lids.
|
|
39
|
+
- Lids contain a content hash. If a line has changed since you read it, the tool rejects the edit and shows the current content; you **MUST** re-read and retry with fresh Lids.
|
|
40
40
|
- After `+TEXT` (or `+`) the cursor advances past the inserted line, so consecutive `+TEXT` ops stack in order. After `Lid=TEXT` the cursor sits on the modified anchor; after `-Lid` it sits on the slot the deleted line vacated. You **MUST** use a fresh `@Lid` / `^Lid` / `^` / `$` to reposition.
|
|
41
41
|
- The tool is syntax-blind: it will not check brackets, indentation, table column counts, or fence integrity. You **MUST** verify indentation-sensitive or structured files after editing (Python, Markdown tables/fences).
|
|
42
42
|
- A section whose PATH does not yet exist creates the file from your `+TEXT` lines (use `^` or `$` then `+TEXT…`). No separate "create file" op is needed.
|
|
@@ -83,7 +83,7 @@ Lid= blank the anchored line's content but KEEP the line (results in an em
|
|
|
83
83
|
\ return (name || DEF).trim().toUpperCase();
|
|
84
84
|
\}
|
|
85
85
|
|
|
86
|
-
# Replace
|
|
86
|
+
# Replace one contiguous block when the existing lines themselves change; the replacement may have more/fewer lines than the selected range
|
|
87
87
|
---a.ts
|
|
88
88
|
{{hrefr 3}}..{{hrefr 6}}=/** Format a display label, falling back to DEF when empty. */
|
|
89
89
|
\export function label(name: string): string {
|
|
@@ -139,6 +139,7 @@ $
|
|
|
139
139
|
- Current/added preview lines include fresh `LINE+hash|content` anchors. Removed preview lines show deleted content and **MUST NOT** be reused as anchors.
|
|
140
140
|
- You **MUST** emit only lines that change. You **MUST NOT** echo unchanged context; the anchor implies position.
|
|
141
141
|
- You **MUST NOT** write `Lid=<sameTextThatIsAlreadyOnThatLine>`; the tool reports a no-op (no change applied). Emit `Lid=TEXT` only when TEXT differs.
|
|
142
|
+
- You **MUST NOT** use `Lid=<originalLineContent>` + `\continuations` as an "insert after" idiom. That form is a *replacement*: its first line lands at the anchor, and its continuations push the original next line down. When the anchor is a closing brace and your continuations also end in `}`, the original line below — often itself `}` (a sibling block, mod, or impl closer) — sits adjacent to yours and you ship a duplicate `}`. For pure insertion, use `@Lid` + `+TEXT…` (after) or `^Lid` + `+TEXT…` (before). Never re-state the anchor's content as the first line of a replacement.
|
|
142
143
|
- A line of the form `Lid|content` (a Lid, then `|`, then text, with NO leading `+`/`-`/`^`/`@`/`\`/`=`/`..`) is **FORBIDDEN**. That shape only appears in `read`/`grep` output as an anchor for *you*; it is never an edit op. If you copy a `Lid|content` line verbatim from a read into a patch, you have made an error — every edit op must start with `+`, `-`, `^`, `@`, `\`, `$`, `!`, or a Lid immediately followed by `=` or `..`.
|
|
143
144
|
- To replace a contiguous block with new content, the canonical form is `LidA..LidB=FIRST_LINE` + `\NEXT_LINE…`. You **MUST NOT** write the old block and then the new block — that is unified-diff thinking and the tool does not understand it. If you find yourself emitting pre-image lines (with or without operators) before your new content, STOP and rewrite the section as a single range-replace.
|
|
144
145
|
- TEXT after `=`, `+`, or `\` includes leading whitespace verbatim. You **MUST NOT** trim or re-indent it.
|
|
@@ -1,25 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
Drives a real Chromium tab with full puppeteer access via JS execution.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- For fetching static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL —
|
|
5
|
-
-
|
|
6
|
-
- `"
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
9
|
-
- `
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- `
|
|
13
|
-
- `
|
|
4
|
+
- For fetching static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL — reader-mode text without spinning up a browser. Use this tool when you need JS execution, authentication, or interactive actions.
|
|
5
|
+
- Three actions only:
|
|
6
|
+
- `open` — acquire (or reuse) a named tab. `name` defaults to `"main"`. Optional `url` navigates after the tab is ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang (default: leave dialogs unhandled — page hangs until caller wires `page.on('dialog', …)`).
|
|
7
|
+
- `close` — release a tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate the process tree (default leaves it running).
|
|
8
|
+
- `run` — execute JS against an existing tab. The `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The function's return value is JSON-stringified into the tool result; multiple `display(value)` calls accumulate text/images.
|
|
9
|
+
- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
|
|
10
|
+
- Browser kinds, selected by the `app` field on `open`:
|
|
11
|
+
- default (no `app`) → headless Chromium with stealth patches.
|
|
12
|
+
- `app.path` → spawn an absolute binary (Electron/CDP). If a running instance already exposes a CDP port, it is reused; otherwise stale instances are killed and a fresh one is spawned. No stealth patches — never tamper with a real desktop app.
|
|
13
|
+
- `app.cdp_url` → connect to an existing CDP endpoint (e.g. `http://127.0.0.1:9222`).
|
|
14
|
+
- `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick a BrowserWindow when the app exposes several.
|
|
15
|
+
- Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover. Available helpers:
|
|
16
|
+
- `tab.goto(url, { waitUntil? })` — clears the element cache and navigates.
|
|
17
|
+
- `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids are stable until the next observe/goto.
|
|
18
|
+
- `tab.id(n)` — resolves an element id from the most recent observe to a real `ElementHandle` you can `.click()`, `.type()`, etc.
|
|
19
|
+
- `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)` — selector-based actions.
|
|
20
|
+
- `tab.waitFor(selector)` — waits until the selector is attached, returns the resolved `ElementHandle` for chaining (e.g. `const btn = await tab.waitFor('text/Submit'); await btn.click();`).
|
|
21
|
+
- `tab.drag(from, to)` — drag from one point to another. Each endpoint is either a selector string (drag center-to-center) or a `{ x, y }` viewport-coordinate point (e.g. for canvases, sliders).
|
|
22
|
+
- `tab.scrollIntoView(selector)` — scroll the matching element to the center of the viewport (use before clicking off-screen elements).
|
|
23
|
+
- `tab.select(selector, …values)` — set the selected option(s) on a `<select>`. Returns the values that ended up selected. `tab.fill` does **NOT** work for selects.
|
|
24
|
+
- `tab.uploadFile(selector, …filePaths)` — attach files to an `<input type="file">`. Paths resolve relative to cwd.
|
|
25
|
+
- `tab.waitForUrl(pattern, { timeout? })` — pattern is a substring or `RegExp`. Polls `location.href` so it works for SPA pushState navigations, not just real navigations. Returns the matched URL.
|
|
26
|
+
- `tab.waitForResponse(pattern, { timeout? })` — pattern is a substring, `RegExp`, or `(response) => boolean`. Returns the raw puppeteer `HTTPResponse` (call `.text()` / `.json()` / `.status()` / `.headers()` on it).
|
|
27
|
+
- `tab.evaluate(fn, …args)` — sugar for `page.evaluate` with the abort signal already wired. Use this instead of dropping to `page.evaluate` for ad-hoc DOM reads.
|
|
28
|
+
- `tab.screenshot({ selector?, fullPage?, save?, silent? })` — auto-attaches the image to the tool output unless `silent: true`. Saves full-res to `save` (or `browser.screenshotDir` setting) and a downscaled copy to the model.
|
|
29
|
+
- `tab.extract(format = "markdown")` — Readability-extracted page content.
|
|
30
|
+
- Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`. Playwright-style `p-aria/[name="…"]`, `p-text/…`, etc. are normalized.
|
|
31
|
+
- Default to `tab.observe()` over `tab.screenshot()` for understanding page state. Screenshot only when visual appearance matters.
|
|
14
32
|
</instruction>
|
|
15
33
|
|
|
16
34
|
<critical>
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
35
|
+
- You **MUST** call `open` before `run`. `run` does not implicitly create a tab.
|
|
36
|
+
- You **MUST NOT** screenshot just to "see what's on the page" — `tab.observe()` returns structured data with element ids you can act on immediately.
|
|
37
|
+
- After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.
|
|
38
|
+
- `code` runs with full Node access. Treat it as your code, not sandboxed code.
|
|
21
39
|
</critical>
|
|
22
40
|
|
|
41
|
+
<examples>
|
|
42
|
+
# Open a tab and read structured page data
|
|
43
|
+
`{"action":"open","name":"docs","url":"https://example.com"}`
|
|
44
|
+
`{"action":"run","name":"docs","code":"const obs = await tab.observe(); display(obs); return obs.elements.length;"}`
|
|
45
|
+
|
|
46
|
+
# Click an observed element by id
|
|
47
|
+
`{"action":"run","name":"docs","code":"const obs = await tab.observe(); const link = obs.elements.find(e => e.role === 'link' && e.name === 'Sign in'); assert(link, 'Sign in link missing'); await (await tab.id(link.id)).click();"}`
|
|
48
|
+
|
|
49
|
+
# Save a full-page screenshot to disk
|
|
50
|
+
`{"action":"run","name":"docs","code":"await tab.screenshot({ fullPage: true, save: 'screenshot.png' });"}`
|
|
51
|
+
|
|
52
|
+
# Fill and submit a form via selectors
|
|
53
|
+
`{"action":"run","name":"docs","code":"await tab.fill('input[name=email]', 'me@example.com'); await tab.click('text/Continue');"}`
|
|
54
|
+
|
|
55
|
+
# Attach to an existing Electron app
|
|
56
|
+
`{"action":"open","name":"cursor","app":{"path":"/Applications/Cursor.app/Contents/MacOS/Cursor"}}`
|
|
57
|
+
|
|
58
|
+
# Close one tab (browser stays alive if other tabs reference it)
|
|
59
|
+
`{"action":"close","name":"docs"}`
|
|
60
|
+
|
|
61
|
+
# Close every tab; leave spawned apps running
|
|
62
|
+
`{"action":"close","all":true}`
|
|
63
|
+
|
|
64
|
+
# Close every tab and kill spawned-app processes too
|
|
65
|
+
`{"action":"close","all":true,"kill":true}`
|
|
66
|
+
</examples>
|
|
67
|
+
|
|
23
68
|
<output>
|
|
24
|
-
|
|
69
|
+
Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.
|
|
25
70
|
</output>
|
|
@@ -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>
|
package/src/prompts/tools/lsp.md
CHANGED
|
@@ -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
|
|
22
|
-
- `new_name`: Required for rename
|
|
23
|
-
- `apply`: Apply edits for rename/code_actions (default true for rename
|
|
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
|
|