@youdie006/prodex 0.16.13 → 0.16.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.
- package/dist/browser-send-lock.js +79 -0
- package/dist/cli-pro.js +5 -4
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdir, open, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
// One visible-browser send at a time per machine: the dedicated Chrome is a
|
|
5
|
+
// single shared tab, and two concurrent prodex clients interleave composer
|
|
6
|
+
// input and navigation, silently cross-contaminating each other's threads
|
|
7
|
+
// (measured live: one client's token prompt landed inside the other client's
|
|
8
|
+
// consult thread, and a 15-minute consult never actually posted).
|
|
9
|
+
function lockPath() {
|
|
10
|
+
const override = process.env.PRODEX_SEND_LOCK_FILE;
|
|
11
|
+
if (override)
|
|
12
|
+
return override;
|
|
13
|
+
return path.join(os.homedir(), ".local", "share", "prodex", "browser-send.lock");
|
|
14
|
+
}
|
|
15
|
+
function holderIsAlive(pid) {
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function readHolderPid(file) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
27
|
+
return typeof parsed.pid === "number" ? parsed.pid : undefined;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function tryAcquire(file) {
|
|
34
|
+
await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
35
|
+
try {
|
|
36
|
+
const handle = await open(file, "wx", 0o600);
|
|
37
|
+
await handle.writeFile(`${JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() })}\n`);
|
|
38
|
+
await handle.close();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code !== "EEXIST")
|
|
43
|
+
throw error;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Serialize visible-browser sends across processes. Waits up to waitMs for a
|
|
49
|
+
* live holder to finish (0 = fail fast); a lock whose holder process is dead
|
|
50
|
+
* is reaped immediately.
|
|
51
|
+
*/
|
|
52
|
+
export async function withBrowserSendLock(waitMs, onWait, fn) {
|
|
53
|
+
const file = lockPath();
|
|
54
|
+
const deadline = Date.now() + Math.max(0, waitMs);
|
|
55
|
+
let waited = false;
|
|
56
|
+
for (;;) {
|
|
57
|
+
if (await tryAcquire(file))
|
|
58
|
+
break;
|
|
59
|
+
const holder = await readHolderPid(file);
|
|
60
|
+
if (holder === undefined || !holderIsAlive(holder)) {
|
|
61
|
+
await rm(file, { force: true }).catch(() => undefined);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (Date.now() >= deadline) {
|
|
65
|
+
throw new Error(`Another prodex browser send is in progress (pid ${holder}). Wait for it to finish, or pass --busy-wait-ms to queue behind it.`);
|
|
66
|
+
}
|
|
67
|
+
if (!waited) {
|
|
68
|
+
waited = true;
|
|
69
|
+
onWait(`another prodex send holds the browser (pid ${holder}); waiting`);
|
|
70
|
+
}
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return await fn();
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await rm(file, { force: true }).catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/cli-pro.js
CHANGED
|
@@ -8,6 +8,7 @@ import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledge
|
|
|
8
8
|
import { formatBrowserDefaults, redactServerUrl } from "./cli-server.js";
|
|
9
9
|
import { errorMessage, firstLine, formatBlockedConsultRecordedMessage, formatProLatestCommand, formatBrowserCheckCommand, formatBrowserLoginCommand, formatBrowserSmokeCommand, formatBrowserTargetAskCommand, formatInitCommand, formatSetupCommand, isMissingFileError, computeSendPacingWaitMs, isUntrustedResultError, resolveMinSendIntervalMs, sourceAwareBrowserBlocker, sourceAwareBrowserNextStep, sourceAwareResultError, sourceAwareResultMessage, sourceAwareSetupMessage } from "./cli-shared.js";
|
|
10
10
|
import { getTokenExpiryStatus, loadBrowserDefaults, loadLocalConfig } from "./config.js";
|
|
11
|
+
import { withBrowserSendLock } from "./browser-send-lock.js";
|
|
11
12
|
import { BridgeStore, MAX_FETCHABLE_RESULT_ARTIFACT_BYTES } from "./store.js";
|
|
12
13
|
export async function runChatgptCommand(rest, io) {
|
|
13
14
|
const [subcommand, ...chatgptArgs] = rest;
|
|
@@ -80,12 +81,12 @@ export async function runChatgptCommand(rest, io) {
|
|
|
80
81
|
};
|
|
81
82
|
let result;
|
|
82
83
|
try {
|
|
83
|
-
result = await sendChatGptPrompt({
|
|
84
|
+
result = await withBrowserSendLock(0, (detail) => io.stderr(`progress: ${detail}`), () => sendChatGptPrompt({
|
|
84
85
|
port,
|
|
85
86
|
prompt: smokePrompt,
|
|
86
87
|
timeoutMs,
|
|
87
88
|
onProgress: createBrowserSendProgressPrinter(io.stderr)
|
|
88
|
-
});
|
|
89
|
+
}));
|
|
89
90
|
}
|
|
90
91
|
catch (error) {
|
|
91
92
|
const blocker = sourceAwareBrowserBlocker(browserSendBlockerFromError(error), sourceCli, commandOptions);
|
|
@@ -655,7 +656,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
655
656
|
}
|
|
656
657
|
throw new Error(formatBlockedConsultRecordedMessage(blocker.message, task.id, sourceCli, { cwd: targetCwd }));
|
|
657
658
|
}
|
|
658
|
-
const sendOnce = () => sendChatGptPrompt({
|
|
659
|
+
const sendOnce = () => withBrowserSendLock(busyWaitMs ?? 0, (detail) => io.stderr(`progress: ${detail}`), () => sendChatGptPrompt({
|
|
659
660
|
port: browserPort,
|
|
660
661
|
prompt: bundle.text,
|
|
661
662
|
targetUrl: normalizedTargetUrl,
|
|
@@ -668,7 +669,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
668
669
|
proMode: selectionProMode,
|
|
669
670
|
effort: selectionEffort,
|
|
670
671
|
onProgress: createBrowserSendProgressPrinter(io.stderr)
|
|
671
|
-
});
|
|
672
|
+
}));
|
|
672
673
|
// One-command recovery: interactive terminals (or explicit --auto-login)
|
|
673
674
|
// launch the dedicated browser and retry once when no browser runs.
|
|
674
675
|
// Scripts and agents keep the plain blocker unless they opt in.
|