@oh-my-pi/pi-coding-agent 14.9.5 → 14.9.8
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 +69 -0
- package/package.json +7 -7
- package/scripts/generate-template.ts +4 -3
- package/src/cli/setup-cli.ts +14 -161
- package/src/cli/stats-cli.ts +56 -2
- package/src/cli.ts +0 -1
- package/src/config/settings-schema.ts +0 -10
- package/src/eval/eval.lark +30 -10
- package/src/eval/js/context-manager.ts +334 -564
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/rewrite-imports.ts +211 -0
- package/src/eval/js/shared/runtime.ts +168 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +2 -4
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/parse.ts +218 -49
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +74 -89
- package/src/eval/py/index.ts +1 -2
- package/src/eval/py/kernel.ts +472 -900
- package/src/eval/py/prelude.py +95 -7
- package/src/eval/py/runner.py +879 -0
- package/src/eval/py/runtime.ts +3 -16
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/export/html/index.ts +5 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +93 -5
- package/src/export/html/template.macro.ts +4 -3
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/components/read-tool-group.ts +9 -0
- package/src/modes/controllers/command-controller.ts +0 -23
- package/src/prompts/tools/eval.md +14 -27
- package/src/prompts/tools/read.md +1 -0
- package/src/session/agent-session.ts +0 -1
- package/src/session/history-storage.ts +77 -19
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +86 -5
- package/src/tools/browser/tab-worker.ts +104 -58
- package/src/tools/conflict-detect.ts +661 -0
- package/src/tools/eval.ts +1 -1
- package/src/tools/index.ts +6 -0
- package/src/tools/path-utils.ts +1 -1
- package/src/tools/read.ts +130 -0
- package/src/tools/write.ts +204 -0
- package/src/web/search/index.ts +6 -4
- package/src/cli/jupyter-cli.ts +0 -106
- package/src/commands/jupyter.ts +0 -32
- package/src/eval/py/cancellation.ts +0 -28
- package/src/eval/py/gateway-coordinator.ts +0 -424
- /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
- /package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import { Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import type { HTMLElement } from "linkedom";
|
|
7
7
|
import type {
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
SerializedAXNode,
|
|
15
15
|
Target,
|
|
16
16
|
} from "puppeteer-core";
|
|
17
|
+
import { JsRuntime, type RuntimeHooks } from "../../eval/js/shared/runtime";
|
|
18
|
+
import type { JsDisplayOutput } from "../../eval/js/shared/types";
|
|
17
19
|
import { resizeImage } from "../../utils/image-resize";
|
|
18
20
|
import { resolveToCwd } from "../path-utils";
|
|
19
21
|
import { formatScreenshot } from "../render-utils";
|
|
@@ -34,6 +36,7 @@ import type {
|
|
|
34
36
|
RunResultOk,
|
|
35
37
|
ScreenshotResult,
|
|
36
38
|
SessionSnapshot,
|
|
39
|
+
ToolReply,
|
|
37
40
|
Transport,
|
|
38
41
|
WorkerInbound,
|
|
39
42
|
WorkerInitPayload,
|
|
@@ -177,6 +180,27 @@ function errorPayload(error: unknown): RunErrorPayload {
|
|
|
177
180
|
return { name: "Error", message: String(error), isToolError: false, isAbort: false };
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
function safeJsonStringify(value: unknown): string {
|
|
184
|
+
try {
|
|
185
|
+
return JSON.stringify(value, null, 2);
|
|
186
|
+
} catch {
|
|
187
|
+
return String(value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function replyError(payload: RunErrorPayload): Error {
|
|
192
|
+
if (payload.isAbort) {
|
|
193
|
+
const err = new ToolAbortError(payload.message || "Tool call aborted");
|
|
194
|
+
if (payload.stack) err.stack = payload.stack;
|
|
195
|
+
return err;
|
|
196
|
+
}
|
|
197
|
+
const Ctor = payload.isToolError ? ToolError : Error;
|
|
198
|
+
const err = new Ctor(payload.message);
|
|
199
|
+
if (payload.name) err.name = payload.name;
|
|
200
|
+
if (payload.stack) err.stack = payload.stack;
|
|
201
|
+
return err;
|
|
202
|
+
}
|
|
203
|
+
|
|
180
204
|
async function targetIdForTarget(target: Target): Promise<string> {
|
|
181
205
|
const raw = target as unknown as { _targetId?: unknown };
|
|
182
206
|
if (typeof raw._targetId === "string") return raw._targetId;
|
|
@@ -361,6 +385,14 @@ async function clickQueryHandlerText(
|
|
|
361
385
|
);
|
|
362
386
|
}
|
|
363
387
|
|
|
388
|
+
interface ActiveRun {
|
|
389
|
+
id: string;
|
|
390
|
+
ac: AbortController;
|
|
391
|
+
displays: RunResultOk["displays"];
|
|
392
|
+
screenshots: ScreenshotResult[];
|
|
393
|
+
pendingTools: Map<string, { resolve(value: unknown): void; reject(error: Error): void }>;
|
|
394
|
+
}
|
|
395
|
+
|
|
364
396
|
export class WorkerCore {
|
|
365
397
|
#transport: Transport;
|
|
366
398
|
#browser?: Browser;
|
|
@@ -368,7 +400,8 @@ export class WorkerCore {
|
|
|
368
400
|
#targetId?: string;
|
|
369
401
|
#elementCache = new Map<number, ElementHandle>();
|
|
370
402
|
#elementCounter = 0;
|
|
371
|
-
#active
|
|
403
|
+
#active: ActiveRun | null = null;
|
|
404
|
+
#runtime: JsRuntime | null = null;
|
|
372
405
|
#unsub: () => void;
|
|
373
406
|
#mode?: WorkerInitPayload["mode"];
|
|
374
407
|
#dialogPolicy?: DialogPolicy;
|
|
@@ -401,6 +434,9 @@ export class WorkerCore {
|
|
|
401
434
|
case "abort":
|
|
402
435
|
if (this.#active?.id === msg.id) this.#active.ac.abort(new ToolAbortError());
|
|
403
436
|
return;
|
|
437
|
+
case "tool-reply":
|
|
438
|
+
this.#deliverToolReply(msg.id, msg.reply);
|
|
439
|
+
return;
|
|
404
440
|
case "close":
|
|
405
441
|
await this.#close();
|
|
406
442
|
return;
|
|
@@ -502,37 +538,26 @@ export class WorkerCore {
|
|
|
502
538
|
const timeoutSignal = AbortSignal.timeout(msg.timeoutMs);
|
|
503
539
|
const ac = new AbortController();
|
|
504
540
|
const signal = AbortSignal.any([timeoutSignal, ac.signal]);
|
|
505
|
-
this.#active = { id: msg.id, ac };
|
|
506
541
|
const displays: RunResultOk["displays"] = [];
|
|
507
542
|
const screenshots: ScreenshotResult[] = [];
|
|
543
|
+
const active: ActiveRun = { id: msg.id, ac, displays, screenshots, pendingTools: new Map() };
|
|
544
|
+
this.#active = active;
|
|
508
545
|
try {
|
|
509
546
|
throwIfAborted(signal);
|
|
510
547
|
const page = this.#requirePage();
|
|
511
548
|
const browser = this.#requireBrowser();
|
|
512
549
|
const tabApi = this.#createTabApi(msg.name, msg.timeoutMs, signal, msg.session, displays, screenshots);
|
|
513
|
-
const
|
|
550
|
+
const runtime = this.#ensureRuntime(msg.session);
|
|
551
|
+
runtime.setCwd(msg.session.cwd);
|
|
552
|
+
runtime.setRunScope({
|
|
514
553
|
page,
|
|
515
554
|
browser,
|
|
516
555
|
tab: tabApi,
|
|
517
|
-
display: (value: unknown): void => this.#display(displays, value),
|
|
518
556
|
assert: (cond: unknown, text?: string): void => {
|
|
519
557
|
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
520
558
|
},
|
|
521
559
|
wait: (ms: number): Promise<void> => Bun.sleep(ms),
|
|
522
|
-
console: this.#console(),
|
|
523
|
-
setTimeout,
|
|
524
|
-
clearTimeout,
|
|
525
|
-
setInterval,
|
|
526
|
-
clearInterval,
|
|
527
|
-
queueMicrotask,
|
|
528
|
-
Promise,
|
|
529
|
-
URL,
|
|
530
|
-
URLSearchParams,
|
|
531
|
-
TextEncoder,
|
|
532
|
-
TextDecoder,
|
|
533
|
-
Buffer,
|
|
534
560
|
});
|
|
535
|
-
const wrapped = `(async () => {\n${msg.code}\n})()`;
|
|
536
561
|
const { promise: cancelRejection, reject: rejectCancel } = Promise.withResolvers<never>();
|
|
537
562
|
const onCancel = (): void => {
|
|
538
563
|
rejectCancel(
|
|
@@ -540,15 +565,17 @@ export class WorkerCore {
|
|
|
540
565
|
? new ToolError(`Browser code execution timed out after ${msg.timeoutMs}ms`)
|
|
541
566
|
: new ToolAbortError(),
|
|
542
567
|
);
|
|
568
|
+
// Cancel in-flight tool calls so user code's awaited proxies reject promptly.
|
|
569
|
+
for (const pending of active.pendingTools.values()) {
|
|
570
|
+
pending.reject(new ToolAbortError());
|
|
571
|
+
}
|
|
572
|
+
active.pendingTools.clear();
|
|
543
573
|
};
|
|
544
574
|
if (signal.aborted) onCancel();
|
|
545
575
|
else signal.addEventListener("abort", onCancel, { once: true });
|
|
546
576
|
try {
|
|
547
577
|
const returnValue = await Promise.race([
|
|
548
|
-
|
|
549
|
-
filename: `browser-run-${msg.id}.js`,
|
|
550
|
-
lineOffset: -1,
|
|
551
|
-
}) as Promise<unknown>,
|
|
578
|
+
runtime.run(msg.code, `browser-run-${msg.id}.js`),
|
|
552
579
|
cancelRejection,
|
|
553
580
|
]);
|
|
554
581
|
await this.#postReadyInfo();
|
|
@@ -564,8 +591,62 @@ export class WorkerCore {
|
|
|
564
591
|
} catch (error) {
|
|
565
592
|
this.#transport.send({ type: "result", id: msg.id, ok: false, error: errorPayload(error) });
|
|
566
593
|
} finally {
|
|
567
|
-
if (this.#active?.id === msg.id) this.#active =
|
|
594
|
+
if (this.#active?.id === msg.id) this.#active = null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
#ensureRuntime(session: SessionSnapshot): JsRuntime {
|
|
599
|
+
if (this.#runtime) return this.#runtime;
|
|
600
|
+
this.#runtime = new JsRuntime({
|
|
601
|
+
initialCwd: session.cwd,
|
|
602
|
+
sessionId: `browser-tab-${this.#targetId ?? "unknown"}`,
|
|
603
|
+
getHooks: () => this.#hooksForActiveRun(),
|
|
604
|
+
});
|
|
605
|
+
return this.#runtime;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#hooksForActiveRun(): RuntimeHooks | null {
|
|
609
|
+
const active = this.#active;
|
|
610
|
+
if (!active) return null;
|
|
611
|
+
return {
|
|
612
|
+
// console.* output stays on the supervisor log channel — matches pre-runtime behavior
|
|
613
|
+
// where browser cells didn't surface `console.log` to the model.
|
|
614
|
+
onText: chunk => this.#log("debug", chunk.replace(/\n$/, "")),
|
|
615
|
+
onDisplay: output => this.#pushDisplay(active.displays, output),
|
|
616
|
+
callTool: (name, args) => this.#callTool(active, name, args),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#pushDisplay(displays: RunResultOk["displays"], output: JsDisplayOutput): void {
|
|
621
|
+
if (output.type === "image") {
|
|
622
|
+
displays.push({ type: "image", data: output.data, mimeType: output.mimeType });
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (output.type === "json") {
|
|
626
|
+
displays.push({ type: "text", text: safeJsonStringify(output.data) });
|
|
627
|
+
return;
|
|
568
628
|
}
|
|
629
|
+
// status — surface as compact JSON so helper side effects (read/write/tree) appear in
|
|
630
|
+
// the cell result alongside explicit display() output.
|
|
631
|
+
displays.push({ type: "text", text: safeJsonStringify(output.event) });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async #callTool(active: ActiveRun, name: string, args: unknown): Promise<unknown> {
|
|
635
|
+
const id = `tab-tc-${active.id}-${crypto.randomUUID()}`;
|
|
636
|
+
const { promise, resolve, reject } = Promise.withResolvers<unknown>();
|
|
637
|
+
active.pendingTools.set(id, { resolve, reject });
|
|
638
|
+
this.#transport.send({ type: "tool-call", id, runId: active.id, name, args });
|
|
639
|
+
return await promise;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#deliverToolReply(id: string, reply: ToolReply): void {
|
|
643
|
+
const active = this.#active;
|
|
644
|
+
if (!active) return;
|
|
645
|
+
const pending = active.pendingTools.get(id);
|
|
646
|
+
if (!pending) return;
|
|
647
|
+
active.pendingTools.delete(id);
|
|
648
|
+
if (reply.ok) pending.resolve(reply.value);
|
|
649
|
+
else pending.reject(replyError(reply.error));
|
|
569
650
|
}
|
|
570
651
|
|
|
571
652
|
#createTabApi(
|
|
@@ -933,41 +1014,6 @@ export class WorkerCore {
|
|
|
933
1014
|
}
|
|
934
1015
|
return handle;
|
|
935
1016
|
}
|
|
936
|
-
|
|
937
|
-
#display(displays: RunResultOk["displays"], value: unknown): void {
|
|
938
|
-
if (value === undefined || value === null) return;
|
|
939
|
-
if (
|
|
940
|
-
typeof value === "object" &&
|
|
941
|
-
value !== null &&
|
|
942
|
-
"type" in (value as Record<string, unknown>) &&
|
|
943
|
-
(value as { type?: unknown }).type === "image"
|
|
944
|
-
) {
|
|
945
|
-
const img = value as { data?: unknown; mimeType?: unknown };
|
|
946
|
-
if (typeof img.data === "string" && typeof img.mimeType === "string") {
|
|
947
|
-
displays.push({ type: "image", data: img.data, mimeType: img.mimeType });
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
if (typeof value === "string") {
|
|
952
|
-
displays.push({ type: "text", text: value });
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
try {
|
|
956
|
-
displays.push({ type: "text", text: JSON.stringify(value, null, 2) });
|
|
957
|
-
} catch {
|
|
958
|
-
displays.push({ type: "text", text: String(value) });
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
#console(): Pick<Console, "log" | "debug" | "warn" | "error"> {
|
|
963
|
-
return {
|
|
964
|
-
log: (...args: unknown[]) => this.#log("debug", args.map(String).join(" ")),
|
|
965
|
-
debug: (...args: unknown[]) => this.#log("debug", args.map(String).join(" ")),
|
|
966
|
-
warn: (...args: unknown[]) => this.#log("warn", args.map(String).join(" ")),
|
|
967
|
-
error: (...args: unknown[]) => this.#log("error", args.map(String).join(" ")),
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
|
|
971
1017
|
#clearElementCache(): void {
|
|
972
1018
|
if (this.#elementCache.size === 0) {
|
|
973
1019
|
this.#elementCounter = 0;
|