@oh-my-pi/pi-coding-agent 14.4.4 → 14.5.0
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/package.json +9 -8
- package/src/edit/modes/atom.ts +15 -4
- package/src/tools/browser.ts +157 -35
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.5.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,12 +46,13 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.20.0",
|
|
48
48
|
"@mozilla/readability": "^0.6.0",
|
|
49
|
-
"@oh-my-pi/omp-stats": "14.
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "14.
|
|
51
|
-
"@oh-my-pi/pi-ai": "14.
|
|
52
|
-
"@oh-my-pi/pi-natives": "14.
|
|
53
|
-
"@oh-my-pi/pi-tui": "14.
|
|
54
|
-
"@oh-my-pi/pi-utils": "14.
|
|
49
|
+
"@oh-my-pi/omp-stats": "14.5.0",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.5.0",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.5.0",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.5.0",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.5.0",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.5.0",
|
|
55
|
+
"@puppeteer/browsers": "^2.13.0",
|
|
55
56
|
"@sinclair/typebox": "^0.34.49",
|
|
56
57
|
"@xterm/headless": "^6.0.0",
|
|
57
58
|
"ajv": "^8.20.0",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"linkedom": "^0.18.12",
|
|
63
64
|
"lru-cache": "11.3.5",
|
|
64
65
|
"markit-ai": "0.5.3",
|
|
65
|
-
"puppeteer": "^24.42.0",
|
|
66
|
+
"puppeteer-core": "^24.42.0",
|
|
66
67
|
"turndown": "7.2.4",
|
|
67
68
|
"turndown-plugin-gfm": "1.0.2",
|
|
68
69
|
"zod": "4.3.6"
|
package/src/edit/modes/atom.ts
CHANGED
|
@@ -355,6 +355,19 @@ function applySedToLine(
|
|
|
355
355
|
}
|
|
356
356
|
if (re?.test(currentLine)) {
|
|
357
357
|
re.lastIndex = 0;
|
|
358
|
+
const probe = re.exec(currentLine);
|
|
359
|
+
re.lastIndex = 0;
|
|
360
|
+
if (probe && probe[0].length === 0) {
|
|
361
|
+
// Zero-length matches (e.g. `()`, `(?=…)`, `^`, `$`) cause `String.replace`
|
|
362
|
+
// to insert the replacement at the match position rather than substitute,
|
|
363
|
+
// which is almost never what models intend. Reject with a pointer to the
|
|
364
|
+
// dedicated insertion verbs.
|
|
365
|
+
return {
|
|
366
|
+
result: currentLine,
|
|
367
|
+
matched: false,
|
|
368
|
+
error: `pattern ${JSON.stringify(spec.pattern)} matches an empty string; use \`pre\`/\`post\`/\`splice\` to insert or replace whole lines, or use a non-empty pattern`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
358
371
|
return { result: currentLine.replace(re, spec.replacement), matched: true };
|
|
359
372
|
}
|
|
360
373
|
// Fall back to literal substring match. Models frequently send sed patterns
|
|
@@ -674,7 +687,7 @@ export function applyAtomEdits(
|
|
|
674
687
|
case "sed": {
|
|
675
688
|
const { result, matched, error, literalFallback } = applySedToLine(currentLine, edit.spec);
|
|
676
689
|
if (error) {
|
|
677
|
-
throw new Error(`Edit sed expression ${JSON.stringify(edit.expression)}
|
|
690
|
+
throw new Error(`Edit sed expression ${JSON.stringify(edit.expression)} rejected: ${error}`);
|
|
678
691
|
}
|
|
679
692
|
if (!matched) {
|
|
680
693
|
throw new Error(
|
|
@@ -786,9 +799,7 @@ export function applyAtomEdits(
|
|
|
786
799
|
}
|
|
787
800
|
if (!anyMatched) {
|
|
788
801
|
if (lastCompileError !== undefined) {
|
|
789
|
-
throw new Error(
|
|
790
|
-
`Edit sed expression ${JSON.stringify(edit.expression)} failed to compile: ${lastCompileError}`,
|
|
791
|
-
);
|
|
802
|
+
throw new Error(`Edit sed expression ${JSON.stringify(edit.expression)} rejected: ${lastCompileError}`);
|
|
792
803
|
}
|
|
793
804
|
throw new Error(`Edit sed expression ${JSON.stringify(edit.expression)} did not match any line in the file.`);
|
|
794
805
|
}
|
package/src/tools/browser.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
Page,
|
|
16
16
|
default as Puppeteer,
|
|
17
17
|
SerializedAXNode,
|
|
18
|
-
} from "puppeteer";
|
|
18
|
+
} from "puppeteer-core";
|
|
19
19
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
20
20
|
import type { ToolSession } from "../sdk";
|
|
21
21
|
import { resizeImage } from "../utils/image-resize";
|
|
@@ -53,7 +53,7 @@ async function loadPuppeteer(): Promise<typeof Puppeteer> {
|
|
|
53
53
|
await Bun.write(path.join(safeDir, "package.json"), "{}");
|
|
54
54
|
try {
|
|
55
55
|
process.chdir(safeDir);
|
|
56
|
-
puppeteerModule = (await import("puppeteer")).default;
|
|
56
|
+
puppeteerModule = (await import("puppeteer-core")).default;
|
|
57
57
|
return puppeteerModule;
|
|
58
58
|
} finally {
|
|
59
59
|
process.chdir(prev);
|
|
@@ -61,50 +61,172 @@ async function loadPuppeteer(): Promise<typeof Puppeteer> {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Lazily download Chromium on first browser launch via @puppeteer/browsers.
|
|
65
|
+
* Skipped when a system Chromium (NixOS) or PUPPETEER_EXECUTABLE_PATH is set.
|
|
66
|
+
* The browser is cached under ~/.omp/puppeteer (getPuppeteerDir).
|
|
67
|
+
*/
|
|
68
|
+
let chromiumExecutablePromise: Promise<string | undefined> | undefined;
|
|
69
|
+
async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
70
|
+
const sysChrome = resolveSystemChromium();
|
|
71
|
+
if (sysChrome) return sysChrome;
|
|
72
|
+
const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
73
|
+
if (envPath) return envPath;
|
|
74
|
+
if (chromiumExecutablePromise) return chromiumExecutablePromise;
|
|
75
|
+
|
|
76
|
+
chromiumExecutablePromise = (async () => {
|
|
77
|
+
const [browsers, revisions] = await Promise.all([
|
|
78
|
+
import("@puppeteer/browsers"),
|
|
79
|
+
import("puppeteer-core/internal/revisions.js"),
|
|
80
|
+
]);
|
|
81
|
+
const platform = browsers.detectBrowserPlatform();
|
|
82
|
+
if (!platform) {
|
|
83
|
+
logger.warn("Could not detect browser platform; relying on puppeteer default resolution");
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const cacheDir = getPuppeteerDir();
|
|
87
|
+
const buildId = await browsers.resolveBuildId(
|
|
88
|
+
browsers.Browser.CHROME,
|
|
89
|
+
platform,
|
|
90
|
+
revisions.PUPPETEER_REVISIONS.chrome,
|
|
91
|
+
);
|
|
92
|
+
const executablePath = browsers.computeExecutablePath({
|
|
93
|
+
browser: browsers.Browser.CHROME,
|
|
94
|
+
buildId,
|
|
95
|
+
cacheDir,
|
|
96
|
+
platform,
|
|
97
|
+
});
|
|
98
|
+
if (fs.existsSync(executablePath)) return executablePath;
|
|
99
|
+
|
|
100
|
+
logger.warn("Downloading Chromium for puppeteer (first browser use)", {
|
|
101
|
+
buildId,
|
|
102
|
+
platform,
|
|
103
|
+
cacheDir,
|
|
104
|
+
});
|
|
105
|
+
let lastReportedPercent = -1;
|
|
106
|
+
await browsers.install({
|
|
107
|
+
browser: browsers.Browser.CHROME,
|
|
108
|
+
buildId,
|
|
109
|
+
cacheDir,
|
|
110
|
+
platform,
|
|
111
|
+
downloadProgressCallback: (downloaded, total) => {
|
|
112
|
+
if (total <= 0) return;
|
|
113
|
+
const pct = Math.floor((downloaded / total) * 100);
|
|
114
|
+
if (pct >= lastReportedPercent + 10 || downloaded === total) {
|
|
115
|
+
lastReportedPercent = pct;
|
|
116
|
+
logger.debug(
|
|
117
|
+
`Chromium download: ${pct}% (${Math.round(downloaded / 1_000_000)} / ${Math.round(total / 1_000_000)} MB)`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return executablePath;
|
|
123
|
+
})().catch(err => {
|
|
124
|
+
chromiumExecutablePromise = undefined;
|
|
125
|
+
throw new ToolError(
|
|
126
|
+
`Failed to install Chromium for puppeteer: ${(err as Error).message}. ` +
|
|
127
|
+
"Set PUPPETEER_EXECUTABLE_PATH to use an existing Chrome/Chromium binary, or install one manually.",
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
return chromiumExecutablePromise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolve a system-installed Chrome/Chromium so `puppeteer.launch()` can reuse
|
|
135
|
+
* it instead of forcing a Chromium download. Returns `undefined` when no binary
|
|
136
|
+
* is found, which lets the caller fall back to a managed download.
|
|
67
137
|
*
|
|
68
|
-
* Detection order:
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
138
|
+
* Detection order (per platform):
|
|
139
|
+
* - macOS: Google Chrome → Chromium → Microsoft Edge (system + user Applications)
|
|
140
|
+
* - Linux: PATH lookups (google-chrome, chromium, etc.) → common /usr/bin paths,
|
|
141
|
+
* with NixOS-specific profile paths added when /etc/NIXOS exists
|
|
142
|
+
* - Windows: Program Files / LocalAppData install paths for Chrome and Edge
|
|
73
143
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
144
|
+
* Honored regardless of platform: PUPPETEER_EXECUTABLE_PATH callers should bypass
|
|
145
|
+
* this entirely (handled in ensureChromiumExecutable).
|
|
76
146
|
*/
|
|
77
147
|
let _resolvedChromium: string | null | undefined; // undefined = unchecked; null = not found
|
|
78
|
-
function
|
|
79
|
-
if (_resolvedChromium !== undefined) return _resolvedChromium ?? undefined;
|
|
148
|
+
function isExecutableFile(p: string): boolean {
|
|
80
149
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
150
|
+
const st = fs.statSync(p);
|
|
151
|
+
return st.isFile();
|
|
85
152
|
} catch {
|
|
86
|
-
|
|
87
|
-
return undefined;
|
|
153
|
+
return false;
|
|
88
154
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function systemChromiumCandidates(): string[] {
|
|
158
|
+
const home = os.homedir();
|
|
159
|
+
const candidates: string[] = [];
|
|
160
|
+
switch (process.platform) {
|
|
161
|
+
case "darwin": {
|
|
162
|
+
for (const root of ["/Applications", path.join(home, "Applications")]) {
|
|
163
|
+
candidates.push(
|
|
164
|
+
path.join(root, "Google Chrome.app/Contents/MacOS/Google Chrome"),
|
|
165
|
+
path.join(root, "Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"),
|
|
166
|
+
path.join(root, "Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev"),
|
|
167
|
+
path.join(root, "Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"),
|
|
168
|
+
path.join(root, "Chromium.app/Contents/MacOS/Chromium"),
|
|
169
|
+
path.join(root, "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "linux": {
|
|
175
|
+
const names = ["google-chrome-stable", "google-chrome", "chromium", "chromium-browser", "chrome"];
|
|
176
|
+
for (const name of names) {
|
|
177
|
+
const found = $which(name);
|
|
178
|
+
if (found) candidates.push(found);
|
|
179
|
+
}
|
|
180
|
+
candidates.push(
|
|
181
|
+
"/usr/bin/google-chrome-stable",
|
|
182
|
+
"/usr/bin/google-chrome",
|
|
183
|
+
"/usr/bin/chromium",
|
|
184
|
+
"/usr/bin/chromium-browser",
|
|
185
|
+
"/snap/bin/chromium",
|
|
186
|
+
"/var/lib/flatpak/exports/bin/com.google.Chrome",
|
|
187
|
+
"/var/lib/flatpak/exports/bin/org.chromium.Chromium",
|
|
188
|
+
);
|
|
189
|
+
let onNixos = false;
|
|
97
190
|
try {
|
|
98
|
-
|
|
99
|
-
_resolvedChromium = candidate;
|
|
100
|
-
logger.debug("NixOS: using system Chromium", { path: candidate });
|
|
101
|
-
return candidate;
|
|
102
|
-
}
|
|
191
|
+
onNixos = fs.existsSync("/etc/NIXOS");
|
|
103
192
|
} catch {}
|
|
193
|
+
if (onNixos) {
|
|
194
|
+
candidates.push(path.join(home, ".nix-profile/bin/chromium"), "/run/current-system/sw/bin/chromium");
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "win32": {
|
|
199
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
200
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
201
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData\\Local");
|
|
202
|
+
candidates.push(
|
|
203
|
+
path.join(programFiles, "Google\\Chrome\\Application\\chrome.exe"),
|
|
204
|
+
path.join(programFilesX86, "Google\\Chrome\\Application\\chrome.exe"),
|
|
205
|
+
path.join(localAppData, "Google\\Chrome\\Application\\chrome.exe"),
|
|
206
|
+
path.join(programFiles, "Chromium\\Application\\chrome.exe"),
|
|
207
|
+
path.join(localAppData, "Chromium\\Application\\chrome.exe"),
|
|
208
|
+
path.join(programFiles, "Microsoft\\Edge\\Application\\msedge.exe"),
|
|
209
|
+
path.join(programFilesX86, "Microsoft\\Edge\\Application\\msedge.exe"),
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return candidates;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveSystemChromium(): string | undefined {
|
|
218
|
+
if (_resolvedChromium !== undefined) return _resolvedChromium ?? undefined;
|
|
219
|
+
const seen = new Set<string>();
|
|
220
|
+
for (const candidate of systemChromiumCandidates()) {
|
|
221
|
+
if (!candidate || seen.has(candidate)) continue;
|
|
222
|
+
seen.add(candidate);
|
|
223
|
+
if (isExecutableFile(candidate)) {
|
|
224
|
+
_resolvedChromium = candidate;
|
|
225
|
+
logger.debug("Using system Chrome/Chromium", { path: candidate });
|
|
226
|
+
return candidate;
|
|
104
227
|
}
|
|
105
228
|
}
|
|
106
229
|
_resolvedChromium = null;
|
|
107
|
-
logger.debug("NixOS detected but no Chromium binary found; Puppeteer may fail to launch");
|
|
108
230
|
return undefined;
|
|
109
231
|
}
|
|
110
232
|
|
|
@@ -674,7 +796,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
674
796
|
this.#browser = await puppeteer.launch({
|
|
675
797
|
headless: this.#currentHeadless,
|
|
676
798
|
defaultViewport: this.#currentHeadless ? initialViewport : null,
|
|
677
|
-
executablePath:
|
|
799
|
+
executablePath: await ensureChromiumExecutable(),
|
|
678
800
|
args: launchArgs,
|
|
679
801
|
ignoreDefaultArgs: [...STEALTH_IGNORE_DEFAULT_ARGS],
|
|
680
802
|
});
|