@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
@@ -0,0 +1,131 @@
1
+ import { DEFAULT_MAX_BYTES, OutputSink } from "../../session/streaming-output";
2
+ import type { ToolSession } from "../../tools";
3
+ import { executeInVmContext, type JsDisplayOutput } from "./context-manager";
4
+
5
+ export interface JsExecutorOptions {
6
+ cwd?: string;
7
+ timeoutMs?: number;
8
+ deadlineMs?: number;
9
+ onChunk?: (chunk: string) => Promise<void> | void;
10
+ signal?: AbortSignal;
11
+ sessionId: string;
12
+ reset?: boolean;
13
+ sessionFile?: string;
14
+ artifactPath?: string;
15
+ artifactId?: string;
16
+ session: ToolSession;
17
+ }
18
+
19
+ export interface JsResult {
20
+ output: string;
21
+ exitCode: number | undefined;
22
+ cancelled: boolean;
23
+ truncated: boolean;
24
+ artifactId?: string;
25
+ totalLines: number;
26
+ totalBytes: number;
27
+ outputLines: number;
28
+ outputBytes: number;
29
+ displayOutputs: JsDisplayOutput[];
30
+ }
31
+
32
+ function getExecutionTimeoutMs(options: Pick<JsExecutorOptions, "deadlineMs" | "timeoutMs">): number | undefined {
33
+ if (options.deadlineMs !== undefined) {
34
+ return Math.max(1, options.deadlineMs - Date.now());
35
+ }
36
+ return options.timeoutMs;
37
+ }
38
+
39
+ function isAbortError(error: unknown): boolean {
40
+ return (
41
+ (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) ||
42
+ (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError"))
43
+ );
44
+ }
45
+
46
+ export async function executeJs(code: string, options: JsExecutorOptions): Promise<JsResult> {
47
+ const displayOutputs: JsDisplayOutput[] = [];
48
+ const outputSink = new OutputSink({
49
+ artifactPath: options.artifactPath,
50
+ artifactId: options.artifactId,
51
+ spillThreshold: DEFAULT_MAX_BYTES,
52
+ onChunk: chunk => options.onChunk?.(chunk),
53
+ });
54
+ const timeoutMs = getExecutionTimeoutMs(options);
55
+ const timeoutSignal =
56
+ typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
57
+ ? AbortSignal.timeout(timeoutMs)
58
+ : undefined;
59
+ const signal =
60
+ options.signal && timeoutSignal
61
+ ? AbortSignal.any([options.signal, timeoutSignal])
62
+ : (options.signal ?? timeoutSignal);
63
+
64
+ try {
65
+ await executeInVmContext({
66
+ sessionKey: options.sessionId,
67
+ sessionId: options.sessionId,
68
+ cwd: options.cwd ?? options.session.cwd,
69
+ session: options.session,
70
+ reset: options.reset,
71
+ code,
72
+ filename: `js-cell-${crypto.randomUUID()}.js`,
73
+ timeoutMs,
74
+ runState: {
75
+ signal,
76
+ onText: chunk => outputSink.push(chunk),
77
+ onDisplay: output => {
78
+ displayOutputs.push(output);
79
+ },
80
+ },
81
+ });
82
+ const summary = await outputSink.dump();
83
+ return {
84
+ output: summary.output,
85
+ exitCode: 0,
86
+ cancelled: false,
87
+ truncated: summary.truncated,
88
+ artifactId: summary.artifactId,
89
+ totalLines: summary.totalLines,
90
+ totalBytes: summary.totalBytes,
91
+ outputLines: summary.outputLines,
92
+ outputBytes: summary.outputBytes,
93
+ displayOutputs,
94
+ };
95
+ } catch (error) {
96
+ if (signal?.aborted || isAbortError(error)) {
97
+ const timeoutReason = timeoutSignal?.aborted ? "Command timed out" : "";
98
+ if (timeoutReason) {
99
+ outputSink.push(timeoutReason);
100
+ }
101
+ const summary = await outputSink.dump();
102
+ return {
103
+ output: summary.output,
104
+ exitCode: undefined,
105
+ cancelled: true,
106
+ truncated: summary.truncated,
107
+ artifactId: summary.artifactId,
108
+ totalLines: summary.totalLines,
109
+ totalBytes: summary.totalBytes,
110
+ outputLines: summary.outputLines,
111
+ outputBytes: summary.outputBytes,
112
+ displayOutputs,
113
+ };
114
+ }
115
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
116
+ outputSink.push(message);
117
+ const summary = await outputSink.dump();
118
+ return {
119
+ output: summary.output,
120
+ exitCode: 1,
121
+ cancelled: false,
122
+ truncated: summary.truncated,
123
+ artifactId: summary.artifactId,
124
+ totalLines: summary.totalLines,
125
+ totalBytes: summary.totalBytes,
126
+ outputLines: summary.outputLines,
127
+ outputBytes: summary.outputBytes,
128
+ displayOutputs,
129
+ };
130
+ }
131
+ }
@@ -0,0 +1,46 @@
1
+ import type { ToolSession } from "../../tools";
2
+ import type { ExecutorBackend, ExecutorBackendExecOptions, ExecutorBackendResult } from "../backend";
3
+ import { executeJs } from "./executor";
4
+
5
+ const JS_SESSION_PREFIX = "js:";
6
+
7
+ function namespaceSessionId(sessionId: string): string {
8
+ return sessionId.startsWith(JS_SESSION_PREFIX) ? sessionId : `${JS_SESSION_PREFIX}${sessionId}`;
9
+ }
10
+
11
+ export default {
12
+ id: "js",
13
+ label: "JavaScript",
14
+ highlightLang: "javascript",
15
+
16
+ async isAvailable(_session: ToolSession): Promise<boolean> {
17
+ return true;
18
+ },
19
+
20
+ async execute(code: string, opts: ExecutorBackendExecOptions): Promise<ExecutorBackendResult> {
21
+ const result = await executeJs(code, {
22
+ cwd: opts.cwd,
23
+ deadlineMs: opts.deadlineMs,
24
+ signal: opts.signal,
25
+ sessionId: namespaceSessionId(opts.sessionId),
26
+ sessionFile: opts.sessionFile,
27
+ reset: opts.reset,
28
+ artifactPath: opts.artifactPath,
29
+ artifactId: opts.artifactId,
30
+ onChunk: opts.onChunk,
31
+ session: opts.session,
32
+ });
33
+ return {
34
+ output: result.output,
35
+ exitCode: result.exitCode,
36
+ cancelled: result.cancelled,
37
+ truncated: result.truncated,
38
+ artifactId: result.artifactId,
39
+ totalLines: result.totalLines,
40
+ totalBytes: result.totalBytes,
41
+ outputLines: result.outputLines,
42
+ outputBytes: result.outputBytes,
43
+ displayOutputs: result.displayOutputs,
44
+ };
45
+ },
46
+ } satisfies ExecutorBackend;
@@ -0,0 +1,2 @@
1
+ import javascriptPrelude from "./prelude.txt" with { type: "text" };
2
+ export const JAVASCRIPT_PRELUDE_SOURCE = javascriptPrelude;
@@ -0,0 +1,84 @@
1
+ if (!globalThis.__omp_js_prelude_loaded__) {
2
+ globalThis.__omp_js_prelude_loaded__ = true;
3
+
4
+ const toOptions = value => (value && typeof value === "object" && !Array.isArray(value) ? value : {});
5
+ const callHelper = (name, ...args) => globalThis.__omp_helpers__[name](...args);
6
+
7
+ const read = (path, opts = {}) => callHelper("read", path, toOptions(opts));
8
+ const write = async (path, data) => callHelper("writeFile", path, data);
9
+ const append = (path, content) => callHelper("append", path, content);
10
+ const stat = path => callHelper("stat", path);
11
+ const grep = (pattern, path, opts = {}) => callHelper("grep", pattern, path, toOptions(opts));
12
+ const rgrep = (pattern, path = ".", opts = {}) => callHelper("rgrep", pattern, path, toOptions(opts));
13
+ const find = (pattern, path = ".", opts = {}) => callHelper("find", pattern, path, toOptions(opts));
14
+ const glob = (pattern, path = ".", opts = {}) => callHelper("glob", pattern, path, toOptions(opts));
15
+ const sort = (text, opts = {}) => callHelper("sortText", text, toOptions(opts));
16
+ const uniq = (text, opts = {}) => callHelper("uniqText", text, toOptions(opts));
17
+ const counter = (items, opts = {}) => callHelper("counter", items, toOptions(opts));
18
+ const sed = (path, pattern, repl, opts = {}) => callHelper("sed", path, pattern, repl, toOptions(opts));
19
+ const diff = (a, b) => callHelper("diff", a, b);
20
+ const tree = (path = ".", opts = {}) => callHelper("tree", path, toOptions(opts));
21
+ const run = (cmd, opts = {}) => callHelper("run", cmd, toOptions(opts));
22
+ const env = (key, value) => callHelper("env", key, value);
23
+
24
+ const tool = new Proxy(
25
+ {},
26
+ {
27
+ get(_target, prop) {
28
+ if (typeof prop !== "string") return undefined;
29
+ return async args => globalThis.__omp_call_tool__(prop, args ?? {});
30
+ },
31
+ },
32
+ );
33
+
34
+ const output = async (...args) => {
35
+ let opts = {};
36
+ let ids = args;
37
+ if (args.length > 0) {
38
+ const last = args.at(-1);
39
+ if (last && typeof last === "object" && !Array.isArray(last)) {
40
+ opts = last;
41
+ ids = args.slice(0, -1);
42
+ }
43
+ }
44
+ const reads = ids.map(id => tool.read({ path: `agent://${id}`, ...opts }));
45
+ const values = await Promise.all(reads);
46
+ return values.length === 1 ? values[0] : values;
47
+ };
48
+
49
+ const display = value => {
50
+ globalThis.__omp_display__(value);
51
+ };
52
+
53
+ const formatArgs = args => args.map(arg => (typeof arg === "string" ? arg : arg));
54
+
55
+ const consoleBridge = {
56
+ log: (...args) => globalThis.__omp_log__("log", ...formatArgs(args)),
57
+ info: (...args) => globalThis.__omp_log__("info", ...formatArgs(args)),
58
+ warn: (...args) => globalThis.__omp_log__("warn", ...formatArgs(args)),
59
+ error: (...args) => globalThis.__omp_log__("error", ...formatArgs(args)),
60
+ debug: (...args) => globalThis.__omp_log__("debug", ...formatArgs(args)),
61
+ };
62
+
63
+ globalThis.console = consoleBridge;
64
+ globalThis.print = consoleBridge.log;
65
+ globalThis.display = display;
66
+ globalThis.tool = tool;
67
+ globalThis.output = output;
68
+ globalThis.read = read;
69
+ globalThis.write = write;
70
+ globalThis.append = append;
71
+ globalThis.stat = stat;
72
+ globalThis.grep = grep;
73
+ globalThis.rgrep = rgrep;
74
+ globalThis.find = find;
75
+ globalThis.glob = glob;
76
+ globalThis.sort = sort;
77
+ globalThis.uniq = uniq;
78
+ globalThis.counter = counter;
79
+ globalThis.sed = sed;
80
+ globalThis.diff = diff;
81
+ globalThis.tree = tree;
82
+ globalThis.run = run;
83
+ globalThis.env = env;
84
+ }
@@ -0,0 +1,124 @@
1
+ import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
+ import type { ToolSession } from "../../tools";
3
+ import { ToolError } from "../../tools/tool-errors";
4
+
5
+ export interface JsStatusEvent {
6
+ op: string;
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ interface ToolBridgeOptions {
11
+ session: ToolSession;
12
+ signal?: AbortSignal;
13
+ emitStatus?: (event: JsStatusEvent) => void;
14
+ }
15
+
16
+ type ToolValue =
17
+ | string
18
+ | {
19
+ text: string;
20
+ details?: unknown;
21
+ images?: Array<{ mimeType: string; data: string }>;
22
+ };
23
+
24
+ function getTool(session: ToolSession, name: string): AgentTool {
25
+ const tool = session.getToolByName?.(name);
26
+ if (!tool) {
27
+ throw new ToolError(`Unknown tool from js runtime: ${name}`);
28
+ }
29
+ return tool;
30
+ }
31
+
32
+ function normalizeArgs(args: unknown): unknown {
33
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
34
+ return args;
35
+ }
36
+ const record = { ...(args as Record<string, unknown>) };
37
+ if (record._i === undefined) {
38
+ record._i = "js prelude";
39
+ }
40
+ return record;
41
+ }
42
+
43
+ function summarizeToolResult(name: string, args: unknown, result: AgentToolResult, text: string): JsStatusEvent {
44
+ const record = (args && typeof args === "object" ? (args as Record<string, unknown>) : {}) as Record<
45
+ string,
46
+ unknown
47
+ >;
48
+ const details = (
49
+ result.details && typeof result.details === "object" ? (result.details as Record<string, unknown>) : {}
50
+ ) as Record<string, unknown>;
51
+
52
+ switch (name) {
53
+ case "read":
54
+ return { op: "read", path: record.path, chars: text.length, preview: text.slice(0, 500) };
55
+ case "write":
56
+ return {
57
+ op: "write",
58
+ path: record.path,
59
+ chars: typeof record.content === "string" ? record.content.length : 0,
60
+ };
61
+ case "grep":
62
+ return {
63
+ op: "grep",
64
+ pattern: record.pattern,
65
+ path: record.path,
66
+ count: details.matchCount ?? undefined,
67
+ };
68
+ case "find":
69
+ return {
70
+ op: "find",
71
+ pattern: record.pattern,
72
+ count: details.fileCount ?? undefined,
73
+ matches: Array.isArray(details.files) ? details.files.slice(0, 20) : undefined,
74
+ };
75
+ case "bash":
76
+ return {
77
+ op: "run",
78
+ cmd: record.command,
79
+ code: typeof details.exitCode === "number" ? details.exitCode : undefined,
80
+ output: text.slice(0, 500),
81
+ };
82
+ default:
83
+ return { op: name, chars: text.length };
84
+ }
85
+ }
86
+
87
+ export async function callSessionTool(name: string, args: unknown, options: ToolBridgeOptions): Promise<ToolValue> {
88
+ const tool = getTool(options.session, name);
89
+ const normalizedArgs = normalizeArgs(args);
90
+ const toolCallId = `js-${name}-${crypto.randomUUID()}`;
91
+ try {
92
+ const result = await tool.execute(toolCallId, normalizedArgs, options.signal);
93
+ const textBlocks = result.content.filter(
94
+ (content): content is { type: "text"; text: string } =>
95
+ content.type === "text" && typeof content.text === "string",
96
+ );
97
+ const imageBlocks = result.content.filter(
98
+ (content): content is { type: "image"; mimeType: string; data: string } =>
99
+ content.type === "image" && typeof content.mimeType === "string" && typeof content.data === "string",
100
+ );
101
+ const text = textBlocks.map(block => block.text).join("");
102
+ options.emitStatus?.(summarizeToolResult(name, normalizedArgs, result, text));
103
+ if (result.details === undefined && imageBlocks.length === 0) {
104
+ return text;
105
+ }
106
+ return {
107
+ text,
108
+ details: result.details,
109
+ images:
110
+ imageBlocks.length > 0
111
+ ? imageBlocks.map(block => ({
112
+ mimeType: block.mimeType,
113
+ data: block.data,
114
+ }))
115
+ : undefined,
116
+ };
117
+ } catch (error) {
118
+ options.emitStatus?.({
119
+ op: name,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ });
122
+ throw error;
123
+ }
124
+ }