@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/package.json +18 -10
  3. package/src/cli/jupyter-cli.ts +1 -1
  4. package/src/config/model-equivalence.ts +49 -16
  5. package/src/config/model-registry.ts +100 -25
  6. package/src/config/model-resolver.ts +29 -15
  7. package/src/config/settings-schema.ts +20 -6
  8. package/src/config/settings.ts +9 -8
  9. package/src/config.ts +9 -0
  10. package/src/eval/backend.ts +43 -0
  11. package/src/eval/eval.lark +43 -0
  12. package/src/eval/index.ts +5 -0
  13. package/src/eval/js/context-manager.ts +717 -0
  14. package/src/eval/js/executor.ts +131 -0
  15. package/src/eval/js/index.ts +46 -0
  16. package/src/eval/js/prelude.ts +2 -0
  17. package/src/eval/js/prelude.txt +84 -0
  18. package/src/eval/js/tool-bridge.ts +124 -0
  19. package/src/eval/parse.ts +337 -0
  20. package/src/{ipy → eval/py}/executor.ts +2 -180
  21. package/src/{ipy → eval/py}/gateway-coordinator.ts +4 -3
  22. package/src/eval/py/index.ts +58 -0
  23. package/src/{ipy → eval/py}/kernel.ts +5 -41
  24. package/src/{ipy → eval/py}/prelude.py +39 -227
  25. package/src/eval/types.ts +48 -0
  26. package/src/export/html/template.generated.ts +1 -1
  27. package/src/export/html/template.js +23 -17
  28. package/src/extensibility/extensions/types.ts +2 -3
  29. package/src/internal-urls/docs-index.generated.ts +5 -5
  30. package/src/lsp/client.ts +9 -0
  31. package/src/lsp/index.ts +395 -0
  32. package/src/lsp/types.ts +15 -4
  33. package/src/main.ts +25 -14
  34. package/src/mcp/oauth-flow.ts +1 -1
  35. package/src/memories/index.ts +1 -1
  36. package/src/modes/acp/acp-event-mapper.ts +1 -1
  37. package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
  38. package/src/modes/components/login-dialog.ts +1 -1
  39. package/src/modes/components/oauth-selector.ts +2 -1
  40. package/src/modes/components/tool-execution.ts +3 -4
  41. package/src/modes/controllers/command-controller.ts +28 -8
  42. package/src/modes/controllers/input-controller.ts +4 -4
  43. package/src/modes/controllers/selector-controller.ts +2 -1
  44. package/src/modes/interactive-mode.ts +4 -5
  45. package/src/modes/types.ts +3 -3
  46. package/src/modes/utils/ui-helpers.ts +2 -2
  47. package/src/prompts/system/system-prompt.md +3 -3
  48. package/src/prompts/tools/atom.md +3 -2
  49. package/src/prompts/tools/browser.md +61 -16
  50. package/src/prompts/tools/eval.md +92 -0
  51. package/src/prompts/tools/lsp.md +7 -3
  52. package/src/sdk.ts +45 -31
  53. package/src/session/agent-session.ts +44 -54
  54. package/src/session/messages.ts +1 -1
  55. package/src/slash-commands/builtin-registry.ts +1 -1
  56. package/src/system-prompt.ts +34 -66
  57. package/src/task/executor.ts +5 -9
  58. package/src/tools/browser/attach.ts +175 -0
  59. package/src/tools/browser/launch.ts +576 -0
  60. package/src/tools/browser/readable.ts +90 -0
  61. package/src/tools/browser/registry.ts +198 -0
  62. package/src/tools/browser/render.ts +212 -0
  63. package/src/tools/browser/tab-protocol.ts +101 -0
  64. package/src/tools/browser/tab-supervisor.ts +429 -0
  65. package/src/tools/browser/tab-worker-entry.ts +21 -0
  66. package/src/tools/browser/tab-worker.ts +1006 -0
  67. package/src/tools/browser.ts +231 -1567
  68. package/src/tools/checkpoint.ts +2 -2
  69. package/src/tools/{python.ts → eval.ts} +324 -315
  70. package/src/tools/exit-plan-mode.ts +1 -1
  71. package/src/tools/index.ts +62 -100
  72. package/src/tools/plan-mode-guard.ts +27 -1
  73. package/src/tools/read.ts +0 -6
  74. package/src/tools/recipe/runners/pkg.ts +34 -32
  75. package/src/tools/renderers.ts +4 -2
  76. package/src/tools/resolve.ts +7 -2
  77. package/src/tools/todo-write.ts +0 -1
  78. package/src/tools/tool-timeouts.ts +2 -2
  79. package/src/utils/markit.ts +15 -7
  80. package/src/utils/tools-manager.ts +5 -5
  81. package/src/web/search/index.ts +5 -5
  82. package/src/web/search/provider.ts +121 -39
  83. package/src/web/search/providers/gemini.ts +2 -2
  84. package/src/web/search/render.ts +2 -2
  85. package/src/ipy/modules.ts +0 -144
  86. package/src/prompts/tools/python.md +0 -57
  87. /package/src/{ipy → eval/py}/cancellation.ts +0 -0
  88. /package/src/{ipy → eval/py}/prelude.ts +0 -0
  89. /package/src/{ipy → eval/py}/runtime.ts +0 -0
@@ -0,0 +1,198 @@
1
+ import * as path from "node:path";
2
+ import { logger } from "@oh-my-pi/pi-utils";
3
+ import type { Subprocess } from "bun";
4
+ import type { Browser, CDPSession } from "puppeteer-core";
5
+ import { ToolAbortError, ToolError } from "../tool-errors";
6
+ import { findFreeCdpPort, findReusableCdp, gracefulKillTreeOnce, killExistingByPath, waitForCdp } from "./attach";
7
+ import { BROWSER_PROTOCOL_TIMEOUT_MS, launchHeadlessBrowser, loadPuppeteer, type UserAgentOverride } from "./launch";
8
+
9
+ export type BrowserKind =
10
+ | { kind: "headless"; headless: boolean }
11
+ | { kind: "spawned"; path: string }
12
+ | { kind: "connected"; cdpUrl: string };
13
+
14
+ export type BrowserKindTag = BrowserKind["kind"];
15
+
16
+ export interface BrowserHandle {
17
+ key: string;
18
+ kind: BrowserKind;
19
+ browser: Browser;
20
+ cdpUrl?: string;
21
+ pid?: number;
22
+ subprocess?: Subprocess;
23
+ refCount: number;
24
+ stealth: { browserSession: CDPSession | null; override: UserAgentOverride | null };
25
+ }
26
+
27
+ const browsers = new Map<string, BrowserHandle>();
28
+
29
+ export function listBrowsers(): BrowserHandle[] {
30
+ return [...browsers.values()];
31
+ }
32
+
33
+ function browserKey(kind: BrowserKind): string {
34
+ switch (kind.kind) {
35
+ case "headless":
36
+ return `headless:${kind.headless ? "1" : "0"}`;
37
+ case "spawned":
38
+ return `spawned:${kind.path}`;
39
+ case "connected":
40
+ return `connected:${kind.cdpUrl}`;
41
+ }
42
+ }
43
+
44
+ export interface AcquireBrowserOptions {
45
+ cwd: string;
46
+ viewport?: { width: number; height: number; deviceScaleFactor?: number };
47
+ appArgs?: string[];
48
+ signal?: AbortSignal;
49
+ }
50
+
51
+ export async function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
52
+ const key = browserKey(kind);
53
+ const existing = browsers.get(key);
54
+ if (existing) {
55
+ if (existing.browser.connected) return existing;
56
+ browsers.delete(key);
57
+ await disposeBrowserHandle(existing, { kill: false });
58
+ }
59
+
60
+ const handle = await openBrowserHandle(kind, opts);
61
+ browsers.set(key, handle);
62
+ return handle;
63
+ }
64
+
65
+ async function openBrowserHandle(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
66
+ if (kind.kind === "headless") {
67
+ const browser = await launchHeadlessBrowser({ headless: kind.headless, viewport: opts.viewport });
68
+ return {
69
+ key: browserKey(kind),
70
+ kind,
71
+ browser,
72
+ refCount: 0,
73
+ stealth: { browserSession: null, override: null },
74
+ };
75
+ }
76
+ if (kind.kind === "connected") {
77
+ const cdpUrl = kind.cdpUrl.replace(/\/+$/, "");
78
+ await waitForCdp(cdpUrl, 5_000, opts.signal);
79
+ const puppeteer = await loadPuppeteer();
80
+ const browser = await puppeteer.connect({
81
+ browserURL: cdpUrl,
82
+ defaultViewport: null,
83
+ protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
84
+ });
85
+ return {
86
+ key: browserKey(kind),
87
+ kind,
88
+ browser,
89
+ cdpUrl,
90
+ refCount: 0,
91
+ stealth: { browserSession: null, override: null },
92
+ };
93
+ }
94
+
95
+ const exe = kind.path;
96
+ if (!path.isAbsolute(exe)) {
97
+ throw new ToolError(
98
+ `app.path must be absolute (got ${JSON.stringify(exe)}). Pass the binary inside Foo.app/Contents/MacOS/, not the .app bundle.`,
99
+ );
100
+ }
101
+ const reused = await findReusableCdp(exe, opts.signal);
102
+ let cdpUrl: string;
103
+ let pid: number;
104
+ let subprocess: Subprocess | undefined;
105
+ if (reused) {
106
+ logger.debug("Reusing existing CDP endpoint for attach", { exe, pid: reused.pid, cdpUrl: reused.cdpUrl });
107
+ cdpUrl = reused.cdpUrl;
108
+ pid = reused.pid;
109
+ } else {
110
+ const killed = await killExistingByPath(exe, opts.signal);
111
+ if (killed > 0) logger.debug("Killed existing instances before attach", { exe, killed });
112
+ const port = await findFreeCdpPort();
113
+ const launchArgs = [...(opts.appArgs ?? []), `--remote-debugging-port=${port}`];
114
+ const child = Bun.spawn([exe, ...launchArgs], {
115
+ stdout: "ignore",
116
+ stderr: "ignore",
117
+ stdin: "ignore",
118
+ });
119
+ child.unref();
120
+ subprocess = child;
121
+ pid = child.pid;
122
+ cdpUrl = `http://127.0.0.1:${port}`;
123
+ try {
124
+ await waitForCdp(cdpUrl, 30_000, opts.signal);
125
+ } catch (err) {
126
+ await gracefulKillTreeOnce(child.pid).catch(() => undefined);
127
+ if (err instanceof ToolAbortError) throw err;
128
+ if (err instanceof Error && err.name === "AbortError") throw err;
129
+ throw new ToolError(`Failed to attach to ${path.basename(exe)} on ${cdpUrl}: ${(err as Error).message}`);
130
+ }
131
+ }
132
+
133
+ const puppeteer = await loadPuppeteer();
134
+ let browser: Browser;
135
+ try {
136
+ browser = await puppeteer.connect({
137
+ browserURL: cdpUrl,
138
+ defaultViewport: null,
139
+ protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
140
+ });
141
+ } catch (err) {
142
+ if (subprocess) await gracefulKillTreeOnce(subprocess.pid);
143
+ throw new ToolError(`Connected to ${cdpUrl} but puppeteer.connect failed: ${(err as Error).message}`);
144
+ }
145
+ return {
146
+ key: browserKey(kind),
147
+ kind,
148
+ browser,
149
+ cdpUrl,
150
+ pid,
151
+ subprocess,
152
+ refCount: 0,
153
+ stealth: { browserSession: null, override: null },
154
+ };
155
+ }
156
+
157
+ export function holdBrowser(handle: BrowserHandle): void {
158
+ handle.refCount++;
159
+ }
160
+
161
+ export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
162
+ handle.refCount = Math.max(0, handle.refCount - 1);
163
+ if (handle.refCount === 0) {
164
+ browsers.delete(handle.key);
165
+ await disposeBrowserHandle(handle, opts);
166
+ }
167
+ }
168
+
169
+ export async function disposeBrowserHandle(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
170
+ if (handle.kind.kind === "headless") {
171
+ if (handle.browser.connected) {
172
+ try {
173
+ await handle.browser.close();
174
+ } catch (err) {
175
+ logger.debug("Failed to close headless browser", { error: (err as Error).message });
176
+ }
177
+ }
178
+ return;
179
+ }
180
+ if (handle.kind.kind === "connected") {
181
+ if (handle.browser.connected) {
182
+ try {
183
+ handle.browser.disconnect();
184
+ } catch (err) {
185
+ logger.debug("Failed to disconnect from remote browser", { error: (err as Error).message });
186
+ }
187
+ }
188
+ return;
189
+ }
190
+ if (handle.browser.connected) {
191
+ try {
192
+ handle.browser.disconnect();
193
+ } catch (err) {
194
+ logger.debug("Failed to disconnect from spawned browser", { error: (err as Error).message });
195
+ }
196
+ }
197
+ if (opts.kill && handle.pid !== undefined) await gracefulKillTreeOnce(handle.pid);
198
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * TUI renderer for the browser tool.
3
+ *
4
+ * Mirrors the `eval` tool look: each `run` invocation is shown as a JS code
5
+ * cell with status icon, optional output, and expand/collapse handling. `open`
6
+ * and `close` actions render as compact status lines.
7
+ */
8
+ import type { Component } from "@oh-my-pi/pi-tui";
9
+ import { Text } from "@oh-my-pi/pi-tui";
10
+ import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
11
+ import type { Theme } from "../../modes/theme/theme";
12
+ import { Hasher, renderCodeCell, renderStatusLine } from "../../tui";
13
+ import type { BrowserToolDetails } from "../browser";
14
+ import { formatStyledTruncationWarning } from "../output-meta";
15
+ import { replaceTabs, shortenPath } from "../render-utils";
16
+
17
+ const BROWSER_DEFAULT_PREVIEW_LINES = 10;
18
+
19
+ interface BrowserRenderArgs {
20
+ action?: "open" | "close" | "run";
21
+ name?: string;
22
+ url?: string;
23
+ code?: string;
24
+ all?: boolean;
25
+ kill?: boolean;
26
+ app?: { path?: string; cdp_url?: string; target?: string };
27
+ viewport?: { width: number; height: number; scale?: number };
28
+ timeout?: number;
29
+ }
30
+
31
+ interface BrowserRenderContext {
32
+ expanded?: boolean;
33
+ previewLines?: number;
34
+ }
35
+
36
+ function describeBrowser(args: BrowserRenderArgs, details: BrowserToolDetails | undefined): string | undefined {
37
+ if (args.app?.cdp_url) return `connected ${args.app.cdp_url}`;
38
+ if (args.app?.path) return `spawned ${shortenPath(args.app.path)}`;
39
+ switch (details?.browser) {
40
+ case "headless":
41
+ return "headless";
42
+ case "spawned":
43
+ return "spawned";
44
+ case "connected":
45
+ return "connected";
46
+ default:
47
+ return undefined;
48
+ }
49
+ }
50
+
51
+ function tabLabel(args: BrowserRenderArgs, details: BrowserToolDetails | undefined): string {
52
+ const name = details?.name ?? args.name ?? "main";
53
+ return `tab ${JSON.stringify(name)}`;
54
+ }
55
+
56
+ function cellStatus(isPartial: boolean, isError: boolean): "pending" | "running" | "complete" | "error" {
57
+ if (isPartial) return "running";
58
+ if (isError) return "error";
59
+ return "complete";
60
+ }
61
+
62
+ function dropTrailingBlankLines(text: string): string {
63
+ return text.replace(/\s+$/, "");
64
+ }
65
+
66
+ function appendLine(component: Component, line: string | undefined): Component {
67
+ if (!line) return component;
68
+ return {
69
+ render: (width: number): string[] => {
70
+ const base = component.render(width);
71
+ return [...base, line];
72
+ },
73
+ invalidate: () => component.invalidate?.(),
74
+ };
75
+ }
76
+
77
+ function renderRunCell(
78
+ args: BrowserRenderArgs,
79
+ details: BrowserToolDetails | undefined,
80
+ options: RenderResultOptions & { renderContext?: BrowserRenderContext },
81
+ output: string,
82
+ isError: boolean,
83
+ theme: Theme,
84
+ ): Component {
85
+ const code = dropTrailingBlankLines(args.code ?? "");
86
+ const status = cellStatus(options.isPartial, isError);
87
+
88
+ const titleParts: string[] = [tabLabel(args, details)];
89
+ const url = details?.url ?? args.url;
90
+ if (url) titleParts.push(shortenPath(url));
91
+ const browserDesc = describeBrowser(args, details);
92
+ if (browserDesc) titleParts.push(browserDesc);
93
+ const title = titleParts.join(" · ");
94
+
95
+ let cached: { key: bigint; width: number; lines: string[] } | undefined;
96
+ return {
97
+ render: (width: number): string[] => {
98
+ const expanded = options.renderContext?.expanded ?? options.expanded;
99
+ const previewLines = options.renderContext?.previewLines ?? BROWSER_DEFAULT_PREVIEW_LINES;
100
+ const key = new Hasher()
101
+ .bool(expanded)
102
+ .bool(isError)
103
+ .u32(previewLines)
104
+ .u32(options.spinnerFrame ?? 0)
105
+ .str(status)
106
+ .str(title)
107
+ .str(code)
108
+ .str(output)
109
+ .digest();
110
+ if (cached && cached.width === width && cached.key === key) {
111
+ return cached.lines;
112
+ }
113
+ const lines = renderCodeCell(
114
+ {
115
+ code,
116
+ language: "javascript",
117
+ title,
118
+ status,
119
+ spinnerFrame: options.spinnerFrame,
120
+ output: output.length > 0 ? output : undefined,
121
+ outputMaxLines: expanded ? Number.POSITIVE_INFINITY : previewLines,
122
+ codeMaxLines: expanded ? Number.POSITIVE_INFINITY : previewLines,
123
+ expanded,
124
+ width,
125
+ },
126
+ theme,
127
+ );
128
+ cached = { key, width, lines };
129
+ return lines;
130
+ },
131
+ invalidate: () => {
132
+ cached = undefined;
133
+ },
134
+ };
135
+ }
136
+
137
+ function renderOpenOrCloseLine(
138
+ args: BrowserRenderArgs,
139
+ details: BrowserToolDetails | undefined,
140
+ isPartial: boolean,
141
+ isError: boolean,
142
+ output: string,
143
+ theme: Theme,
144
+ ): Component {
145
+ const action = (details?.action ?? args.action ?? "open") as "open" | "close" | "run";
146
+ const status = cellStatus(isPartial, isError);
147
+ const icon =
148
+ status === "complete" ? "success" : status === "error" ? "error" : status === "running" ? "running" : "pending";
149
+
150
+ let title: string;
151
+ if (action === "close") {
152
+ const all = args.all === true || (args.name === undefined && details?.name === undefined);
153
+ title = all ? "Close all tabs" : `Close ${tabLabel(args, details)}`;
154
+ if (args.kill) title += " (kill)";
155
+ } else {
156
+ title = `Open ${tabLabel(args, details)}`;
157
+ }
158
+
159
+ const meta: string[] = [];
160
+ const browserDesc = describeBrowser(args, details);
161
+ if (browserDesc) meta.push(browserDesc);
162
+ const url = details?.url ?? args.url;
163
+ if (url) meta.push(shortenPath(url));
164
+
165
+ const header = renderStatusLine({ icon, title, meta }, theme);
166
+ if (!output) return new Text(header, 0, 0);
167
+ const outputLines = output.split("\n").map(line => theme.fg("toolOutput", replaceTabs(line)));
168
+ return new Text([header, ...outputLines].join("\n"), 0, 0);
169
+ }
170
+
171
+ function extractTextOutput(content: Array<{ type: string; text?: string }> | undefined): string {
172
+ if (!content) return "";
173
+ const text = content
174
+ .filter(c => c.type === "text")
175
+ .map(c => c.text ?? "")
176
+ .join("\n");
177
+ return dropTrailingBlankLines(text);
178
+ }
179
+
180
+ export const browserToolRenderer = {
181
+ renderCall(args: BrowserRenderArgs, options: RenderResultOptions, theme: Theme): Component {
182
+ const action = args.action;
183
+ if (action === "run") {
184
+ return renderRunCell(args, undefined, options, "", false, theme);
185
+ }
186
+ return renderOpenOrCloseLine(args, undefined, options.isPartial, false, "", theme);
187
+ },
188
+ renderResult(
189
+ result: { content: Array<{ type: string; text?: string }>; details?: BrowserToolDetails; isError?: boolean },
190
+ options: RenderResultOptions & { renderContext?: BrowserRenderContext },
191
+ theme: Theme,
192
+ args?: BrowserRenderArgs,
193
+ ): Component {
194
+ const argsObj = args ?? {};
195
+ const details = result.details;
196
+ const action = details?.action ?? argsObj.action;
197
+ const isError = result.isError === true;
198
+ const output = extractTextOutput(result.content);
199
+
200
+ if (action === "run") {
201
+ let component = renderRunCell(argsObj, details, options, output, isError, theme);
202
+ const truncationWarning = details?.meta?.truncation
203
+ ? (formatStyledTruncationWarning(details.meta, theme) ?? undefined)
204
+ : undefined;
205
+ component = appendLine(component, truncationWarning);
206
+ return component;
207
+ }
208
+ return renderOpenOrCloseLine(argsObj, details, options.isPartial, isError, output, theme);
209
+ },
210
+ mergeCallAndResult: true,
211
+ inline: true,
212
+ };
@@ -0,0 +1,101 @@
1
+ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
2
+
3
+ export type Transferable = Bun.Transferable;
4
+
5
+ export interface ObservationEntry {
6
+ id: number;
7
+ role: string;
8
+ name?: string;
9
+ value?: string | number;
10
+ description?: string;
11
+ keyshortcuts?: string;
12
+ states: string[];
13
+ }
14
+
15
+ export interface Observation {
16
+ url: string;
17
+ title?: string;
18
+ viewport: { width: number; height: number; deviceScaleFactor?: number };
19
+ scroll: {
20
+ x: number;
21
+ y: number;
22
+ width: number;
23
+ height: number;
24
+ scrollWidth: number;
25
+ scrollHeight: number;
26
+ };
27
+ elements: ObservationEntry[];
28
+ }
29
+
30
+ export interface ScreenshotResult {
31
+ dest: string;
32
+ mimeType: string;
33
+ bytes: number;
34
+ width: number;
35
+ height: number;
36
+ }
37
+
38
+ export interface SessionSnapshot {
39
+ cwd: string;
40
+ browserScreenshotDir?: string;
41
+ }
42
+
43
+ export type WorkerInitPayload =
44
+ | {
45
+ mode: "headless";
46
+ browserWSEndpoint: string;
47
+ safeDir: string;
48
+ viewport?: { width: number; height: number; deviceScaleFactor?: number };
49
+ dialogs?: "accept" | "dismiss";
50
+ url?: string;
51
+ waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
52
+ timeoutMs: number;
53
+ }
54
+ | {
55
+ mode: "attach";
56
+ browserWSEndpoint: string;
57
+ safeDir: string;
58
+ targetId: string;
59
+ dialogs?: "accept" | "dismiss";
60
+ };
61
+
62
+ export type WorkerInbound =
63
+ | { type: "init"; payload: WorkerInitPayload }
64
+ | { type: "run"; id: string; name: string; code: string; timeoutMs: number; session: SessionSnapshot }
65
+ | { type: "abort"; id: string }
66
+ | { type: "close" };
67
+
68
+ export interface ReadyInfo {
69
+ url: string;
70
+ title?: string;
71
+ viewport: { width: number; height: number; deviceScaleFactor?: number };
72
+ targetId: string;
73
+ }
74
+
75
+ export interface RunResultOk {
76
+ displays: Array<TextContent | ImageContent>;
77
+ returnValue: unknown;
78
+ screenshots: ScreenshotResult[];
79
+ }
80
+
81
+ export interface RunErrorPayload {
82
+ name: string;
83
+ message: string;
84
+ stack?: string;
85
+ isToolError: boolean;
86
+ isAbort: boolean;
87
+ }
88
+
89
+ export type WorkerOutbound =
90
+ | { type: "ready"; info: ReadyInfo }
91
+ | { type: "init-failed"; error: RunErrorPayload }
92
+ | { type: "result"; id: string; ok: true; payload: RunResultOk }
93
+ | { type: "result"; id: string; ok: false; error: RunErrorPayload }
94
+ | { type: "log"; level: "debug" | "warn" | "error"; msg: string; meta?: Record<string, unknown> }
95
+ | { type: "closed" };
96
+
97
+ export interface Transport {
98
+ send(msg: WorkerOutbound | WorkerInbound, transferList?: Transferable[]): void;
99
+ onMessage(handler: (msg: WorkerOutbound | WorkerInbound) => void): () => void;
100
+ close(): void;
101
+ }