pi-oracle 0.7.8 → 0.7.9
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 +10 -0
- package/extensions/oracle/worker/run-job.mjs +143 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.9 - 2026-06-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- made oracle workers launch their isolated Chrome runtime directly and attach `agent-browser` via DevTools, avoiding failures when unrelated `agent-browser` sessions or daemons are already running
|
|
9
|
+
- tightened worker-owned browser cleanup so runtime profiles are deleted only after the isolated Chrome process has been closed or terminated
|
|
10
|
+
- rejected `browser.args` overrides that would bypass oracle-managed Chrome profile or DevTools isolation
|
|
11
|
+
|
|
12
|
+
### Validation
|
|
13
|
+
- verified ChatGPT and Grok oracle smoke tests against the local source extension after the worker-owned browser launch fix
|
|
14
|
+
|
|
5
15
|
## 0.7.8 - 2026-06-11
|
|
6
16
|
|
|
7
17
|
### Changed
|
|
@@ -85,12 +85,15 @@ const POST_SEND_SETTLE_MS = 15_000;
|
|
|
85
85
|
const AGENT_BROWSER_BIN = [process.env.AGENT_BROWSER_PATH, "/opt/homebrew/bin/agent-browser", "/usr/local/bin/agent-browser"].find(
|
|
86
86
|
(candidate) => typeof candidate === "string" && candidate && existsSync(candidate),
|
|
87
87
|
) || "agent-browser";
|
|
88
|
+
const CHROME_DEVTOOLS_READY_TIMEOUT_MS = 15_000;
|
|
88
89
|
const CP_BIN = process.env.PI_ORACLE_CP_PATH?.trim() || "cp";
|
|
89
90
|
scrubSweetCookieSafeStoragePasswordEnv();
|
|
90
91
|
|
|
91
92
|
let cpSupportsApfsCloneFlag;
|
|
92
93
|
let currentJob;
|
|
93
94
|
let browserStarted = false;
|
|
95
|
+
let browserProcess;
|
|
96
|
+
let browserProcessError;
|
|
94
97
|
let cleaningUpBrowser = false;
|
|
95
98
|
let cleaningUpRuntime = false;
|
|
96
99
|
let shuttingDown = false;
|
|
@@ -355,16 +358,24 @@ async function cleanupRuntime(job) {
|
|
|
355
358
|
cleaningUpRuntime = true;
|
|
356
359
|
const warnings = [];
|
|
357
360
|
try {
|
|
361
|
+
let browserClosed = true;
|
|
358
362
|
await closeBrowser(job).catch(async (error) => {
|
|
363
|
+
browserClosed = false;
|
|
359
364
|
const message = `Browser close warning during cleanup: ${error instanceof Error ? error.message : String(error)}`;
|
|
360
365
|
warnings.push(message);
|
|
361
366
|
await log(message).catch(() => undefined);
|
|
362
367
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
+
if (browserClosed) {
|
|
369
|
+
try {
|
|
370
|
+
assertSafeRuntimeProfilePath(job.runtimeProfileDir, "runtime profile", job.config);
|
|
371
|
+
await rm(job.runtimeProfileDir, { recursive: true, force: true });
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const message = `Runtime profile cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
374
|
+
warnings.push(message);
|
|
375
|
+
await log(message).catch(() => undefined);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
const message = `Runtime profile cleanup skipped because isolated browser close did not complete: ${job.runtimeProfileDir}`;
|
|
368
379
|
warnings.push(message);
|
|
369
380
|
await log(message).catch(() => undefined);
|
|
370
381
|
}
|
|
@@ -542,6 +553,39 @@ function browserBaseArgs(job, options = {}) {
|
|
|
542
553
|
return args;
|
|
543
554
|
}
|
|
544
555
|
|
|
556
|
+
function waitForChildClose(child, timeoutMs) {
|
|
557
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
|
|
558
|
+
return new Promise((resolve) => {
|
|
559
|
+
let settled = false;
|
|
560
|
+
const timer = setTimeout(() => {
|
|
561
|
+
if (settled) return;
|
|
562
|
+
settled = true;
|
|
563
|
+
resolve(false);
|
|
564
|
+
}, timeoutMs);
|
|
565
|
+
timer.unref?.();
|
|
566
|
+
child.once("close", () => {
|
|
567
|
+
if (settled) return;
|
|
568
|
+
settled = true;
|
|
569
|
+
clearTimeout(timer);
|
|
570
|
+
resolve(true);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async function terminateBrowserProcess() {
|
|
576
|
+
if (!browserProcess) return;
|
|
577
|
+
const child = browserProcess;
|
|
578
|
+
browserProcess = undefined;
|
|
579
|
+
browserProcessError = undefined;
|
|
580
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
581
|
+
killProcessTree(child);
|
|
582
|
+
if (await waitForChildClose(child, 2_000)) return;
|
|
583
|
+
killProcess(child);
|
|
584
|
+
if (!(await waitForChildClose(child, 2_000))) {
|
|
585
|
+
throw new Error(`Timed out terminating isolated Chrome process ${child.pid ?? "(unknown pid)"}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
545
589
|
async function closeBrowser(job) {
|
|
546
590
|
if (cleaningUpBrowser) return;
|
|
547
591
|
cleaningUpBrowser = true;
|
|
@@ -554,15 +598,107 @@ async function closeBrowser(job) {
|
|
|
554
598
|
throw new Error(result.stderr || result.stdout || `agent-browser close exited with code ${result.code}`);
|
|
555
599
|
}
|
|
556
600
|
} finally {
|
|
601
|
+
await terminateBrowserProcess();
|
|
557
602
|
browserStarted = false;
|
|
558
603
|
cleaningUpBrowser = false;
|
|
559
604
|
}
|
|
560
605
|
}
|
|
561
606
|
|
|
607
|
+
function assertSafeBrowserLaunchArg(arg) {
|
|
608
|
+
const value = String(arg).trim().toLowerCase();
|
|
609
|
+
const managedFlags = [
|
|
610
|
+
"--user-data-dir",
|
|
611
|
+
"--remote-debugging-port",
|
|
612
|
+
"--remote-debugging-pipe",
|
|
613
|
+
"--remote-debugging-address",
|
|
614
|
+
"--remote-allow-origins",
|
|
615
|
+
];
|
|
616
|
+
const flag = managedFlags.find((candidate) => value === candidate || value.startsWith(`${candidate}=`) || value.startsWith(`${candidate} `));
|
|
617
|
+
if (flag) {
|
|
618
|
+
throw new Error(`browser.args cannot override oracle-managed Chrome launch isolation flag ${flag}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function safeBrowserLaunchArgs(job) {
|
|
623
|
+
if (!Array.isArray(job.config.browser.args)) return [];
|
|
624
|
+
for (const arg of job.config.browser.args) assertSafeBrowserLaunchArg(arg);
|
|
625
|
+
return job.config.browser.args;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function chromeLaunchArgs(job, url) {
|
|
629
|
+
const args = [
|
|
630
|
+
"--remote-debugging-port=0",
|
|
631
|
+
"--remote-allow-origins=*",
|
|
632
|
+
"--no-first-run",
|
|
633
|
+
"--no-default-browser-check",
|
|
634
|
+
"--disable-background-networking",
|
|
635
|
+
"--disable-backgrounding-occluded-windows",
|
|
636
|
+
"--disable-component-update",
|
|
637
|
+
"--disable-default-apps",
|
|
638
|
+
"--disable-hang-monitor",
|
|
639
|
+
"--disable-popup-blocking",
|
|
640
|
+
"--disable-prompt-on-repost",
|
|
641
|
+
"--disable-sync",
|
|
642
|
+
"--disable-features=Translate",
|
|
643
|
+
"--enable-features=NetworkService,NetworkServiceInProcess",
|
|
644
|
+
"--metrics-recording-only",
|
|
645
|
+
"--password-store=basic",
|
|
646
|
+
"--use-mock-keychain",
|
|
647
|
+
"--enable-unsafe-swiftshader",
|
|
648
|
+
"--window-size=1280,720",
|
|
649
|
+
`--user-data-dir=${job.runtimeProfileDir}`,
|
|
650
|
+
];
|
|
651
|
+
if (job.config.browser.runMode !== "headed") args.push("--headless=new", "--hide-scrollbars");
|
|
652
|
+
if (job.config.browser.userAgent) args.push(`--user-agent=${job.config.browser.userAgent}`);
|
|
653
|
+
args.push(...safeBrowserLaunchArgs(job));
|
|
654
|
+
args.push(url);
|
|
655
|
+
return args;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async function waitForDevToolsEndpoint(job) {
|
|
659
|
+
const path = join(job.runtimeProfileDir, "DevToolsActivePort");
|
|
660
|
+
const startedAt = Date.now();
|
|
661
|
+
while (Date.now() - startedAt < CHROME_DEVTOOLS_READY_TIMEOUT_MS) {
|
|
662
|
+
if (browserProcessError) {
|
|
663
|
+
throw new Error(`Chrome failed before DevTools became available: ${browserProcessError instanceof Error ? browserProcessError.message : String(browserProcessError)}`);
|
|
664
|
+
}
|
|
665
|
+
if (browserProcess?.exitCode !== null && browserProcess?.exitCode !== undefined) {
|
|
666
|
+
throw new Error(`Chrome exited before DevTools became available (exit code ${browserProcess.exitCode}).`);
|
|
667
|
+
}
|
|
668
|
+
if (existsSync(path)) {
|
|
669
|
+
const lines = (await readFile(path, "utf8")).trim().split(/\r?\n/);
|
|
670
|
+
const port = lines[0]?.trim();
|
|
671
|
+
const browserPath = lines[1]?.trim();
|
|
672
|
+
if (/^\d+$/.test(port)) {
|
|
673
|
+
return browserPath ? `ws://127.0.0.1:${port}${browserPath}` : `http://127.0.0.1:${port}`;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
await sleep(100);
|
|
677
|
+
}
|
|
678
|
+
throw new Error(`Timed out waiting for Chrome DevTools endpoint at ${path}.`);
|
|
679
|
+
}
|
|
680
|
+
|
|
562
681
|
async function launchBrowser(job, url) {
|
|
563
682
|
await closeBrowser(job);
|
|
564
|
-
const
|
|
565
|
-
|
|
683
|
+
const executablePath = job.config.browser.executablePath;
|
|
684
|
+
if (!executablePath) throw new Error("Oracle requires browser.executablePath when launching isolated browser runtimes without owning the global agent-browser daemon.");
|
|
685
|
+
const args = chromeLaunchArgs(job, url);
|
|
686
|
+
await log(`Launching isolated Chrome directly for agent-browser attach: ${JSON.stringify([executablePath, ...args])}`);
|
|
687
|
+
browserProcessError = undefined;
|
|
688
|
+
browserProcess = spawn(executablePath, args, {
|
|
689
|
+
env: sweetCookieSafeStoragePasswordScrubbedEnv(),
|
|
690
|
+
stdio: "ignore",
|
|
691
|
+
detached: false,
|
|
692
|
+
shell: false,
|
|
693
|
+
});
|
|
694
|
+
browserProcess.on("error", (error) => {
|
|
695
|
+
browserProcessError = error;
|
|
696
|
+
log(`Chrome process error: ${error instanceof Error ? error.message : String(error)}`).catch(() => undefined);
|
|
697
|
+
});
|
|
698
|
+
const endpoint = await waitForDevToolsEndpoint(job);
|
|
699
|
+
await log(`Connecting agent-browser session ${job.runtimeSessionName} to isolated Chrome DevTools endpoint`);
|
|
700
|
+
await spawnCommand(AGENT_BROWSER_BIN, [...browserBaseArgs(job), "connect", endpoint]);
|
|
701
|
+
await spawnCommand(AGENT_BROWSER_BIN, [...browserBaseArgs(job), "open", url]);
|
|
566
702
|
browserStarted = true;
|
|
567
703
|
}
|
|
568
704
|
|
package/package.json
CHANGED