open-agents-ai 0.186.64 → 0.186.66
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/dist/index.js +146 -44
- package/dist/scripts/web_scrape.py +22 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -247398,11 +247398,31 @@ async function autoLaunchStation(port = 2020) {
|
|
|
247398
247398
|
return false;
|
|
247399
247399
|
return new Promise((resolvePromise) => {
|
|
247400
247400
|
const child = spawn8(pythonBin, [launcherScript, "--port", String(port)], {
|
|
247401
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
247402
|
-
detached: true
|
|
247401
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
247403
247402
|
});
|
|
247404
247403
|
stationProcess = child;
|
|
247405
|
-
|
|
247404
|
+
const cleanupStation = () => {
|
|
247405
|
+
if (stationProcess && stationProcess.pid && !stationProcess.killed) {
|
|
247406
|
+
try {
|
|
247407
|
+
process.kill(-stationProcess.pid, "SIGKILL");
|
|
247408
|
+
} catch {
|
|
247409
|
+
}
|
|
247410
|
+
try {
|
|
247411
|
+
stationProcess.kill("SIGKILL");
|
|
247412
|
+
} catch {
|
|
247413
|
+
}
|
|
247414
|
+
stationProcess = null;
|
|
247415
|
+
}
|
|
247416
|
+
};
|
|
247417
|
+
process.on("exit", cleanupStation);
|
|
247418
|
+
process.on("SIGTERM", () => {
|
|
247419
|
+
cleanupStation();
|
|
247420
|
+
process.exit(0);
|
|
247421
|
+
});
|
|
247422
|
+
process.on("SIGINT", () => {
|
|
247423
|
+
cleanupStation();
|
|
247424
|
+
process.exit(0);
|
|
247425
|
+
});
|
|
247406
247426
|
let resolved = false;
|
|
247407
247427
|
const timer = setTimeout(() => {
|
|
247408
247428
|
if (!resolved) {
|
|
@@ -247575,9 +247595,9 @@ var init_vision = __esm({
|
|
|
247575
247595
|
if (ollamaResult)
|
|
247576
247596
|
return ollamaResult;
|
|
247577
247597
|
try {
|
|
247578
|
-
const { execSync:
|
|
247598
|
+
const { execSync: execSync38 } = await import("node:child_process");
|
|
247579
247599
|
try {
|
|
247580
|
-
|
|
247600
|
+
execSync38("pip3 install --user moondream 2>/dev/null || pip install --user moondream 2>/dev/null", {
|
|
247581
247601
|
timeout: 12e4,
|
|
247582
247602
|
stdio: "pipe"
|
|
247583
247603
|
});
|
|
@@ -247590,7 +247610,7 @@ var init_vision = __esm({
|
|
|
247590
247610
|
} catch {
|
|
247591
247611
|
}
|
|
247592
247612
|
try {
|
|
247593
|
-
|
|
247613
|
+
execSync38("ollama pull moondream", { timeout: 3e5, stdio: "pipe" });
|
|
247594
247614
|
const retryOllama = await this.tryOllamaVision(buffer2, filename, action, prompt, length4, start2);
|
|
247595
247615
|
if (retryOllama)
|
|
247596
247616
|
return retryOllama;
|
|
@@ -247698,8 +247718,8 @@ Coordinates are normalized (0-1). Multiply by image width/height for pixel value
|
|
|
247698
247718
|
const errText = await res.text().catch(() => "");
|
|
247699
247719
|
if (res.status === 404 || /not found|does not exist/i.test(errText)) {
|
|
247700
247720
|
try {
|
|
247701
|
-
const { execSync:
|
|
247702
|
-
|
|
247721
|
+
const { execSync: execSync38 } = await import("node:child_process");
|
|
247722
|
+
execSync38("ollama pull moondream", { timeout: 3e5, stdio: "pipe" });
|
|
247703
247723
|
res = await fetch(`${ollamaHost}/api/generate`, {
|
|
247704
247724
|
method: "POST",
|
|
247705
247725
|
headers: { "Content-Type": "application/json" },
|
|
@@ -249000,7 +249020,6 @@ async function launchService() {
|
|
|
249000
249020
|
}
|
|
249001
249021
|
serviceProcess = spawn9(python, [SCRAPE_SCRIPT], {
|
|
249002
249022
|
stdio: "ignore",
|
|
249003
|
-
detached: true,
|
|
249004
249023
|
env: {
|
|
249005
249024
|
...process.env,
|
|
249006
249025
|
SCRAPE_PORT: String(DEFAULT_PORT),
|
|
@@ -249008,7 +249027,28 @@ async function launchService() {
|
|
|
249008
249027
|
SCRAPE_REQUIRE_AUTH: "0"
|
|
249009
249028
|
}
|
|
249010
249029
|
});
|
|
249011
|
-
|
|
249030
|
+
const cleanupService = () => {
|
|
249031
|
+
if (serviceProcess && serviceProcess.pid && !serviceProcess.killed) {
|
|
249032
|
+
try {
|
|
249033
|
+
process.kill(-serviceProcess.pid, "SIGKILL");
|
|
249034
|
+
} catch {
|
|
249035
|
+
}
|
|
249036
|
+
try {
|
|
249037
|
+
serviceProcess.kill("SIGKILL");
|
|
249038
|
+
} catch {
|
|
249039
|
+
}
|
|
249040
|
+
serviceProcess = null;
|
|
249041
|
+
}
|
|
249042
|
+
};
|
|
249043
|
+
process.on("exit", cleanupService);
|
|
249044
|
+
process.on("SIGTERM", () => {
|
|
249045
|
+
cleanupService();
|
|
249046
|
+
process.exit(0);
|
|
249047
|
+
});
|
|
249048
|
+
process.on("SIGINT", () => {
|
|
249049
|
+
cleanupService();
|
|
249050
|
+
process.exit(0);
|
|
249051
|
+
});
|
|
249012
249052
|
for (let i2 = 0; i2 < 120; i2++) {
|
|
249013
249053
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
249014
249054
|
if (await probeService())
|
|
@@ -253231,10 +253271,21 @@ function spawnFullSubAgent(task, opts, onOutput, onComplete) {
|
|
|
253231
253271
|
NO_COLOR: "1",
|
|
253232
253272
|
CI: "true"
|
|
253233
253273
|
},
|
|
253234
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
253235
|
-
detached: true
|
|
253274
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
253236
253275
|
});
|
|
253237
|
-
|
|
253276
|
+
const cleanupChild = () => {
|
|
253277
|
+
if (child.pid && !child.killed) {
|
|
253278
|
+
try {
|
|
253279
|
+
process.kill(-child.pid, "SIGKILL");
|
|
253280
|
+
} catch {
|
|
253281
|
+
}
|
|
253282
|
+
try {
|
|
253283
|
+
child.kill("SIGKILL");
|
|
253284
|
+
} catch {
|
|
253285
|
+
}
|
|
253286
|
+
}
|
|
253287
|
+
};
|
|
253288
|
+
process.on("exit", cleanupChild);
|
|
253238
253289
|
const outputBuffer = [];
|
|
253239
253290
|
const entry = {
|
|
253240
253291
|
id,
|
|
@@ -260684,7 +260735,7 @@ ${transcript}`
|
|
|
260684
260735
|
const buffer2 = Buffer.from(rawBase64, "base64");
|
|
260685
260736
|
let resizedBase64 = null;
|
|
260686
260737
|
try {
|
|
260687
|
-
const { execSync:
|
|
260738
|
+
const { execSync: execSync38 } = await import("node:child_process");
|
|
260688
260739
|
const { writeFileSync: writeFileSync33, readFileSync: readFileSync49, unlinkSync: unlinkSync13 } = await import("node:fs");
|
|
260689
260740
|
const { join: join83 } = await import("node:path");
|
|
260690
260741
|
const { tmpdir: tmpdir11 } = await import("node:os");
|
|
@@ -260694,7 +260745,7 @@ ${transcript}`
|
|
|
260694
260745
|
const pyBin = process.platform === "win32" ? "python" : "python3";
|
|
260695
260746
|
const escapedIn = tmpIn.replace(/\\/g, "\\\\");
|
|
260696
260747
|
const escapedOut = tmpOut.replace(/\\/g, "\\\\");
|
|
260697
|
-
|
|
260748
|
+
execSync38(`${pyBin} -c "from PIL import Image; img = Image.open('${escapedIn}'); img.thumbnail((512, 512), Image.LANCZOS); img = img.convert('RGB'); img.save('${escapedOut}', 'JPEG', quality=75)"`, { timeout: 1e4, stdio: "pipe" });
|
|
260698
260749
|
const resizedBuf = readFileSync49(tmpOut);
|
|
260699
260750
|
resizedBase64 = `data:image/jpeg;base64,${resizedBuf.toString("base64")}`;
|
|
260700
260751
|
try {
|
|
@@ -260748,8 +260799,8 @@ ${transcript}`
|
|
|
260748
260799
|
if (!res.ok && model === "moondream" && res.status === 404) {
|
|
260749
260800
|
this.emit({ type: "status", content: `Pulling moondream vision model...`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
260750
260801
|
try {
|
|
260751
|
-
const { execSync:
|
|
260752
|
-
|
|
260802
|
+
const { execSync: execSync38 } = await import("node:child_process");
|
|
260803
|
+
execSync38("ollama pull moondream", { timeout: 3e5, stdio: "pipe" });
|
|
260753
260804
|
res = await fetch(`${ollamaHost}/api/generate`, {
|
|
260754
260805
|
method: "POST",
|
|
260755
260806
|
headers: { "Content-Type": "application/json" },
|
|
@@ -285666,7 +285717,7 @@ async function handlePeerEndpoint(peerId, authKey, ctx3, local) {
|
|
|
285666
285717
|
}
|
|
285667
285718
|
}
|
|
285668
285719
|
async function handleParallel(arg, ctx3) {
|
|
285669
|
-
const { execSync:
|
|
285720
|
+
const { execSync: execSync38 } = await import("node:child_process");
|
|
285670
285721
|
const baseUrl = ctx3.config.backendUrl || "http://localhost:11434";
|
|
285671
285722
|
const isRemote = ctx3.config.backendType === "nexus";
|
|
285672
285723
|
if (isRemote) {
|
|
@@ -285690,7 +285741,7 @@ async function handleParallel(arg, ctx3) {
|
|
|
285690
285741
|
}
|
|
285691
285742
|
let systemdVal = "";
|
|
285692
285743
|
try {
|
|
285693
|
-
const out =
|
|
285744
|
+
const out = execSync38("systemctl show ollama.service -p Environment 2>/dev/null || true", { encoding: "utf8" });
|
|
285694
285745
|
const match = out.match(/OLLAMA_NUM_PARALLEL=(\d+)/);
|
|
285695
285746
|
if (match)
|
|
285696
285747
|
systemdVal = match[1];
|
|
@@ -285719,7 +285770,7 @@ async function handleParallel(arg, ctx3) {
|
|
|
285719
285770
|
}
|
|
285720
285771
|
const isSystemd = (() => {
|
|
285721
285772
|
try {
|
|
285722
|
-
const out =
|
|
285773
|
+
const out = execSync38("systemctl is-active ollama.service 2>/dev/null", { encoding: "utf8" }).trim();
|
|
285723
285774
|
return out === "active" || out === "inactive";
|
|
285724
285775
|
} catch {
|
|
285725
285776
|
return false;
|
|
@@ -285733,10 +285784,10 @@ async function handleParallel(arg, ctx3) {
|
|
|
285733
285784
|
const overrideContent = `[Service]
|
|
285734
285785
|
Environment="OLLAMA_NUM_PARALLEL=${n2}"
|
|
285735
285786
|
`;
|
|
285736
|
-
|
|
285737
|
-
|
|
285738
|
-
|
|
285739
|
-
|
|
285787
|
+
execSync38(`sudo mkdir -p ${overrideDir}`, { stdio: "pipe" });
|
|
285788
|
+
execSync38(`echo '${overrideContent}' | sudo tee ${overrideFile} > /dev/null`, { stdio: "pipe" });
|
|
285789
|
+
execSync38("sudo systemctl daemon-reload", { stdio: "pipe" });
|
|
285790
|
+
execSync38("sudo systemctl restart ollama.service", { stdio: "pipe" });
|
|
285740
285791
|
let ready = false;
|
|
285741
285792
|
for (let i2 = 0; i2 < 30 && !ready; i2++) {
|
|
285742
285793
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
@@ -285763,7 +285814,7 @@ Environment="OLLAMA_NUM_PARALLEL=${n2}"
|
|
|
285763
285814
|
renderInfo(`Setting OLLAMA_NUM_PARALLEL=${n2}...`);
|
|
285764
285815
|
try {
|
|
285765
285816
|
try {
|
|
285766
|
-
|
|
285817
|
+
execSync38("pkill -f 'ollama serve' 2>/dev/null || true", { stdio: "pipe" });
|
|
285767
285818
|
} catch {
|
|
285768
285819
|
}
|
|
285769
285820
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
@@ -286724,18 +286775,18 @@ async function showExposeDashboard(gateway, rl, ctx3) {
|
|
|
286724
286775
|
const cmd = `/endpoint ${id} --auth ${gateway.authKey ?? ""}`;
|
|
286725
286776
|
let copied = false;
|
|
286726
286777
|
try {
|
|
286727
|
-
const { execSync:
|
|
286778
|
+
const { execSync: execSync38 } = __require("node:child_process");
|
|
286728
286779
|
const platform6 = process.platform;
|
|
286729
286780
|
if (platform6 === "darwin") {
|
|
286730
|
-
|
|
286781
|
+
execSync38("pbcopy", { input: cmd, timeout: 3e3 });
|
|
286731
286782
|
copied = true;
|
|
286732
286783
|
} else if (platform6 === "win32") {
|
|
286733
|
-
|
|
286784
|
+
execSync38("clip", { input: cmd, timeout: 3e3 });
|
|
286734
286785
|
copied = true;
|
|
286735
286786
|
} else {
|
|
286736
286787
|
for (const tool of ["xclip -selection clipboard", "xsel --clipboard --input", "wl-copy"]) {
|
|
286737
286788
|
try {
|
|
286738
|
-
|
|
286789
|
+
execSync38(tool, { input: cmd, timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
286739
286790
|
copied = true;
|
|
286740
286791
|
break;
|
|
286741
286792
|
} catch {
|
|
@@ -300099,7 +300150,7 @@ import * as https3 from "node:https";
|
|
|
300099
300150
|
import { createRequire as createRequire4 } from "node:module";
|
|
300100
300151
|
import { fileURLToPath as fileURLToPath15 } from "node:url";
|
|
300101
300152
|
import { dirname as dirname23, join as join76, resolve as resolve33 } from "node:path";
|
|
300102
|
-
import { spawn as spawn22 } from "node:child_process";
|
|
300153
|
+
import { spawn as spawn22, execSync as execSync36 } from "node:child_process";
|
|
300103
300154
|
import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, readFileSync as readFileSync46, readdirSync as readdirSync22, existsSync as existsSync58 } from "node:fs";
|
|
300104
300155
|
import { randomBytes as randomBytes19, randomUUID as randomUUID5 } from "node:crypto";
|
|
300105
300156
|
function getVersion3() {
|
|
@@ -300984,8 +301035,8 @@ async function handleV1Run(req2, res) {
|
|
|
300984
301035
|
env: runEnv,
|
|
300985
301036
|
stdio: ["ignore", "pipe", "pipe"],
|
|
300986
301037
|
detached: true
|
|
301038
|
+
// Own process group so we can kill the whole tree with -pid
|
|
300987
301039
|
});
|
|
300988
|
-
child.unref();
|
|
300989
301040
|
job.sandbox = sandbox;
|
|
300990
301041
|
}
|
|
300991
301042
|
job.pid = child.pid ?? 0;
|
|
@@ -301092,11 +301143,31 @@ function handleV1RunsDelete(res, id) {
|
|
|
301092
301143
|
}
|
|
301093
301144
|
if (job.status === "running") {
|
|
301094
301145
|
const child = runningProcesses.get(id);
|
|
301095
|
-
|
|
301096
|
-
|
|
301097
|
-
} else if (job.pid > 0) {
|
|
301146
|
+
const pid = child?.pid ?? job.pid;
|
|
301147
|
+
if (pid > 0) {
|
|
301098
301148
|
try {
|
|
301099
|
-
process.kill(
|
|
301149
|
+
process.kill(-pid, "SIGTERM");
|
|
301150
|
+
} catch {
|
|
301151
|
+
}
|
|
301152
|
+
try {
|
|
301153
|
+
process.kill(pid, "SIGTERM");
|
|
301154
|
+
} catch {
|
|
301155
|
+
}
|
|
301156
|
+
setTimeout(() => {
|
|
301157
|
+
try {
|
|
301158
|
+
process.kill(-pid, "SIGKILL");
|
|
301159
|
+
} catch {
|
|
301160
|
+
}
|
|
301161
|
+
try {
|
|
301162
|
+
process.kill(pid, "SIGKILL");
|
|
301163
|
+
} catch {
|
|
301164
|
+
}
|
|
301165
|
+
}, 3e3);
|
|
301166
|
+
}
|
|
301167
|
+
const containerName = `oa-${id}`;
|
|
301168
|
+
if (job.sandbox === "container") {
|
|
301169
|
+
try {
|
|
301170
|
+
execSync36(`docker stop ${containerName}`, { timeout: 5e3, stdio: "ignore" });
|
|
301100
301171
|
} catch {
|
|
301101
301172
|
}
|
|
301102
301173
|
}
|
|
@@ -301947,11 +302018,34 @@ function startApiServer(options2 = {}) {
|
|
|
301947
302018
|
const shutdown = () => {
|
|
301948
302019
|
log22("\n Shutting down API server...\n");
|
|
301949
302020
|
for (const [id, child] of runningProcesses) {
|
|
301950
|
-
|
|
301951
|
-
|
|
302021
|
+
const pid = child.pid;
|
|
302022
|
+
if (pid && pid > 0) {
|
|
302023
|
+
try {
|
|
302024
|
+
process.kill(-pid, "SIGTERM");
|
|
302025
|
+
} catch {
|
|
302026
|
+
}
|
|
302027
|
+
try {
|
|
302028
|
+
process.kill(pid, "SIGTERM");
|
|
302029
|
+
} catch {
|
|
302030
|
+
}
|
|
301952
302031
|
}
|
|
301953
302032
|
runningProcesses.delete(id);
|
|
301954
302033
|
}
|
|
302034
|
+
setTimeout(() => {
|
|
302035
|
+
for (const [, child] of runningProcesses) {
|
|
302036
|
+
const pid = child.pid;
|
|
302037
|
+
if (pid && pid > 0) {
|
|
302038
|
+
try {
|
|
302039
|
+
process.kill(-pid, "SIGKILL");
|
|
302040
|
+
} catch {
|
|
302041
|
+
}
|
|
302042
|
+
try {
|
|
302043
|
+
process.kill(pid, "SIGKILL");
|
|
302044
|
+
} catch {
|
|
302045
|
+
}
|
|
302046
|
+
}
|
|
302047
|
+
}
|
|
302048
|
+
}, 2e3).unref();
|
|
301955
302049
|
server.close(() => {
|
|
301956
302050
|
log22(" Server stopped.\n");
|
|
301957
302051
|
process.exit(0);
|
|
@@ -302013,7 +302107,7 @@ import { createRequire as createRequire5 } from "node:module";
|
|
|
302013
302107
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
302014
302108
|
import { readFileSync as readFileSync47, writeFileSync as writeFileSync30, appendFileSync as appendFileSync6, rmSync as rmSync3, readdirSync as readdirSync23, mkdirSync as mkdirSync32 } from "node:fs";
|
|
302015
302109
|
import { existsSync as existsSync59 } from "node:fs";
|
|
302016
|
-
import { execSync as
|
|
302110
|
+
import { execSync as execSync37 } from "node:child_process";
|
|
302017
302111
|
import { homedir as homedir21 } from "node:os";
|
|
302018
302112
|
function formatTimeAgo(date) {
|
|
302019
302113
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
@@ -305783,7 +305877,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
305783
305877
|
try {
|
|
305784
305878
|
if (process.platform === "win32") {
|
|
305785
305879
|
try {
|
|
305786
|
-
|
|
305880
|
+
execSync37(`taskkill /F /PID ${pid}`, { timeout: 5e3, stdio: "ignore" });
|
|
305787
305881
|
} catch {
|
|
305788
305882
|
}
|
|
305789
305883
|
} else {
|
|
@@ -305810,7 +305904,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
305810
305904
|
if (pid > 0) {
|
|
305811
305905
|
if (process.platform === "win32") {
|
|
305812
305906
|
try {
|
|
305813
|
-
|
|
305907
|
+
execSync37(`taskkill /F /PID ${pid}`, { timeout: 5e3, stdio: "ignore" });
|
|
305814
305908
|
} catch {
|
|
305815
305909
|
}
|
|
305816
305910
|
} else {
|
|
@@ -305827,7 +305921,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
305827
305921
|
} catch {
|
|
305828
305922
|
}
|
|
305829
305923
|
try {
|
|
305830
|
-
|
|
305924
|
+
execSync37(process.platform === "win32" ? "timeout /t 1 /nobreak >nul" : "sleep 0.5", { timeout: 3e3, stdio: "ignore" });
|
|
305831
305925
|
} catch {
|
|
305832
305926
|
}
|
|
305833
305927
|
const oaPath = join77(repoRoot, OA_DIR);
|
|
@@ -305841,14 +305935,14 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
305841
305935
|
} catch (err) {
|
|
305842
305936
|
if (attempt < 2) {
|
|
305843
305937
|
try {
|
|
305844
|
-
|
|
305938
|
+
execSync37(process.platform === "win32" ? "timeout /t 1 /nobreak >nul" : "sleep 0.3", { timeout: 3e3, stdio: "ignore" });
|
|
305845
305939
|
} catch {
|
|
305846
305940
|
}
|
|
305847
305941
|
} else {
|
|
305848
305942
|
writeContent(() => renderWarning(`Could not fully remove ${OA_DIR}/: ${err instanceof Error ? err.message : String(err)}`));
|
|
305849
305943
|
if (process.platform === "win32") {
|
|
305850
305944
|
try {
|
|
305851
|
-
|
|
305945
|
+
execSync37(`rd /s /q "${oaPath}"`, { timeout: 1e4, stdio: "ignore" });
|
|
305852
305946
|
deleted = true;
|
|
305853
305947
|
} catch {
|
|
305854
305948
|
}
|
|
@@ -307250,8 +307344,16 @@ async function runBackground(task, config, opts) {
|
|
|
307250
307344
|
env: { ...process.env, OA_JOB_ID: id },
|
|
307251
307345
|
stdio: ["ignore", "pipe", "pipe"],
|
|
307252
307346
|
detached: true
|
|
307347
|
+
// Own process group for tree kill
|
|
307348
|
+
});
|
|
307349
|
+
process.on("exit", () => {
|
|
307350
|
+
if (child.pid && !child.killed) {
|
|
307351
|
+
try {
|
|
307352
|
+
process.kill(-child.pid, "SIGKILL");
|
|
307353
|
+
} catch {
|
|
307354
|
+
}
|
|
307355
|
+
}
|
|
307253
307356
|
});
|
|
307254
|
-
child.unref();
|
|
307255
307357
|
job.pid = child.pid ?? 0;
|
|
307256
307358
|
writeFileSync31(join78(dir, `${id}.json`), JSON.stringify(job, null, 2));
|
|
307257
307359
|
let output = "";
|
|
@@ -773,11 +773,33 @@ _clean_thread.start()
|
|
|
773
773
|
|
|
774
774
|
@atexit.register
|
|
775
775
|
def _shutdown_cleanup():
|
|
776
|
+
"""Clean up ALL resources: Chrome browser, frame cache thread."""
|
|
776
777
|
_CLEAN_STOP.set()
|
|
778
|
+
# CRITICAL: Close the Chrome browser to prevent orphaned Chrome processes
|
|
779
|
+
try:
|
|
780
|
+
Tools.close_browser()
|
|
781
|
+
except Exception:
|
|
782
|
+
pass
|
|
777
783
|
with contextlib.suppress(Exception):
|
|
778
784
|
_clean_thread.join(timeout=2.0)
|
|
779
785
|
|
|
780
786
|
|
|
787
|
+
# Signal handlers: ensure Chrome is killed on SIGTERM/SIGINT
|
|
788
|
+
import signal as _signal
|
|
789
|
+
|
|
790
|
+
def _handle_terminate(signum, frame):
|
|
791
|
+
"""Graceful shutdown on SIGTERM/SIGINT — close Chrome then exit."""
|
|
792
|
+
try:
|
|
793
|
+
Tools.close_browser()
|
|
794
|
+
except Exception:
|
|
795
|
+
pass
|
|
796
|
+
_CLEAN_STOP.set()
|
|
797
|
+
raise SystemExit(0)
|
|
798
|
+
|
|
799
|
+
_signal.signal(_signal.SIGTERM, _handle_terminate)
|
|
800
|
+
_signal.signal(_signal.SIGINT, _handle_terminate)
|
|
801
|
+
|
|
802
|
+
|
|
781
803
|
# ──────────────────────────────────────────────────────────────
|
|
782
804
|
# 7) Utility responses
|
|
783
805
|
# ──────────────────────────────────────────────────────────────
|
package/package.json
CHANGED