@steipete/oracle 0.8.6 → 0.10.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/LICENSE +1 -1
- package/README.md +130 -45
- package/dist/bin/oracle-cli.js +613 -379
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +314 -104
- package/dist/src/browser/actions/navigation.js +161 -136
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +452 -303
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
- package/dist/src/browser/providers/index.js +2 -0
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +65 -45
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +7 -4
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +11 -0
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +12 -8
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +145 -87
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +37 -25
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +182 -79
- package/dist/src/cli/sessionLineage.js +60 -0
- package/dist/src/cli/sessionRunner.js +118 -90
- package/dist/src/cli/sessionTable.js +28 -24
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +140 -127
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +80 -0
- package/dist/src/gemini-web/client.js +81 -64
- package/dist/src/gemini-web/executionMode.js +16 -0
- package/dist/src/gemini-web/executor.js +327 -169
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +81 -64
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +84 -46
- package/dist/src/oracle/config.js +124 -58
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +69 -45
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -30
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +23 -15
- package/dist/src/oracle/run.js +172 -140
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +81 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +69 -65
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
|
@@ -6,7 +6,7 @@ export function normalizeBrowserModelStrategy(value) {
|
|
|
6
6
|
if (!normalized) {
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
|
-
if (normalized ===
|
|
9
|
+
if (normalized === "select" || normalized === "current" || normalized === "ignore") {
|
|
10
10
|
return normalized;
|
|
11
11
|
}
|
|
12
12
|
throw new Error(`Invalid browser model strategy: "${value}". Expected "select", "current", or "ignore".`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { navigateToChatGPT, navigateToPromptReadyWithFallback, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, installJavaScriptDialogAutoDismissal, } from
|
|
2
|
-
export { ensureModelSelection } from
|
|
3
|
-
export { submitPrompt, clearPromptComposer } from
|
|
4
|
-
export { clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, } from
|
|
5
|
-
export { waitForAssistantResponse, readAssistantSnapshot, captureAssistantMarkdown, buildAssistantExtractorForTest, buildConversationDebugExpressionForTest, buildMarkdownFallbackExtractorForTest, buildCopyExpressionForTest, } from
|
|
1
|
+
export { navigateToChatGPT, navigateToPromptReadyWithFallback, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, installJavaScriptDialogAutoDismissal, } from "./actions/navigation.js";
|
|
2
|
+
export { ensureModelSelection } from "./actions/modelSelection.js";
|
|
3
|
+
export { submitPrompt, clearPromptComposer } from "./actions/promptComposer.js";
|
|
4
|
+
export { clearComposerAttachments, uploadAttachmentFile, waitForAttachmentCompletion, waitForUserTurnAttachments, buildUserTurnAttachmentExpressionForTest, } from "./actions/attachments.js";
|
|
5
|
+
export { waitForAssistantResponse, readAssistantSnapshot, captureAssistantMarkdown, buildAssistantExtractorForTest, buildAssistantSnapshotExpressionForTest, buildConversationDebugExpressionForTest, buildMarkdownFallbackExtractorForTest, buildCopyExpressionForTest, } from "./actions/assistantResponse.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { formatFileSection } from
|
|
1
|
+
import { formatFileSection } from "../oracle/markdown.js";
|
|
2
2
|
export function buildAttachmentPlan(sections, { inlineFiles, bundleRequested, maxAttachments = 10, }) {
|
|
3
3
|
if (inlineFiles) {
|
|
4
4
|
const inlineLines = [];
|
|
5
5
|
sections.forEach((section) => {
|
|
6
|
-
inlineLines.push(formatFileSection(section.displayPath, section.content).trimEnd(),
|
|
6
|
+
inlineLines.push(formatFileSection(section.displayPath, section.content).trimEnd(), "");
|
|
7
7
|
});
|
|
8
|
-
const inlineBlock = inlineLines.join(
|
|
8
|
+
const inlineBlock = inlineLines.join("\n").trim();
|
|
9
9
|
return {
|
|
10
|
-
mode:
|
|
10
|
+
mode: "inline",
|
|
11
11
|
inlineBlock,
|
|
12
12
|
inlineFileCount: sections.length,
|
|
13
13
|
attachments: [],
|
|
@@ -17,12 +17,12 @@ export function buildAttachmentPlan(sections, { inlineFiles, bundleRequested, ma
|
|
|
17
17
|
const attachments = sections.map((section) => ({
|
|
18
18
|
path: section.absolutePath,
|
|
19
19
|
displayPath: section.displayPath,
|
|
20
|
-
sizeBytes: Buffer.byteLength(section.content,
|
|
20
|
+
sizeBytes: Buffer.byteLength(section.content, "utf8"),
|
|
21
21
|
}));
|
|
22
22
|
const shouldBundle = bundleRequested || attachments.length > maxAttachments;
|
|
23
23
|
return {
|
|
24
|
-
mode: shouldBundle ?
|
|
25
|
-
inlineBlock:
|
|
24
|
+
mode: shouldBundle ? "bundle" : "upload",
|
|
25
|
+
inlineBlock: "",
|
|
26
26
|
inlineFileCount: 0,
|
|
27
27
|
attachments,
|
|
28
28
|
shouldBundle,
|
|
@@ -30,14 +30,17 @@ export function buildAttachmentPlan(sections, { inlineFiles, bundleRequested, ma
|
|
|
30
30
|
}
|
|
31
31
|
export function buildCookiePlan(config) {
|
|
32
32
|
if (config?.inlineCookies && config.inlineCookies.length > 0) {
|
|
33
|
-
const source = config.inlineCookiesSource ??
|
|
34
|
-
return {
|
|
33
|
+
const source = config.inlineCookiesSource ?? "inline";
|
|
34
|
+
return {
|
|
35
|
+
type: "inline",
|
|
36
|
+
description: `Cookies: inline payload (${config.inlineCookies.length}) via ${source}.`,
|
|
37
|
+
};
|
|
35
38
|
}
|
|
36
39
|
if (config?.cookieSync === false) {
|
|
37
|
-
return { type:
|
|
40
|
+
return { type: "disabled", description: "Cookies: sync disabled (--browser-no-cookie-sync)." };
|
|
38
41
|
}
|
|
39
42
|
const allowlist = config?.cookieNames && config.cookieNames.length > 0
|
|
40
|
-
? config.cookieNames.join(
|
|
41
|
-
:
|
|
42
|
-
return { type:
|
|
43
|
+
? config.cookieNames.join(", ")
|
|
44
|
+
: "all from Chrome profile";
|
|
45
|
+
return { type: "copy", description: `Cookies: copy from Chrome (${allowlist}).` };
|
|
43
46
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import { randomUUID } from
|
|
3
|
-
import { mkdir, readFile, rm, writeFile } from
|
|
4
|
-
import { execFile } from
|
|
5
|
-
import { promisify } from
|
|
6
|
-
import { delay } from
|
|
7
|
-
const DEVTOOLS_ACTIVE_PORT_FILENAME =
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { delay } from "./utils.js";
|
|
7
|
+
const DEVTOOLS_ACTIVE_PORT_FILENAME = "DevToolsActivePort";
|
|
8
8
|
const DEVTOOLS_ACTIVE_PORT_RELATIVE_PATHS = [
|
|
9
9
|
DEVTOOLS_ACTIVE_PORT_FILENAME,
|
|
10
|
-
path.join(
|
|
10
|
+
path.join("Default", DEVTOOLS_ACTIVE_PORT_FILENAME),
|
|
11
11
|
];
|
|
12
|
-
const CHROME_PID_FILENAME =
|
|
13
|
-
const ORACLE_PROFILE_LOCK_FILENAME =
|
|
12
|
+
const CHROME_PID_FILENAME = "chrome.pid";
|
|
13
|
+
const ORACLE_PROFILE_LOCK_FILENAME = "oracle-automation.lock";
|
|
14
14
|
const execFileAsync = promisify(execFile);
|
|
15
15
|
export function getDevToolsActivePortPaths(userDataDir) {
|
|
16
16
|
return DEVTOOLS_ACTIVE_PORT_RELATIVE_PATHS.map((relative) => path.join(userDataDir, relative));
|
|
@@ -18,9 +18,9 @@ export function getDevToolsActivePortPaths(userDataDir) {
|
|
|
18
18
|
export async function readDevToolsPort(userDataDir) {
|
|
19
19
|
for (const candidate of getDevToolsActivePortPaths(userDataDir)) {
|
|
20
20
|
try {
|
|
21
|
-
const raw = await readFile(candidate,
|
|
21
|
+
const raw = await readFile(candidate, "utf8");
|
|
22
22
|
const firstLine = raw.split(/\r?\n/u)[0]?.trim();
|
|
23
|
-
const port = Number.parseInt(firstLine ??
|
|
23
|
+
const port = Number.parseInt(firstLine ?? "", 10);
|
|
24
24
|
if (Number.isFinite(port)) {
|
|
25
25
|
return port;
|
|
26
26
|
}
|
|
@@ -36,7 +36,7 @@ export async function writeDevToolsActivePort(userDataDir, port) {
|
|
|
36
36
|
for (const candidate of getDevToolsActivePortPaths(userDataDir)) {
|
|
37
37
|
try {
|
|
38
38
|
await mkdir(path.dirname(candidate), { recursive: true });
|
|
39
|
-
await writeFile(candidate, contents,
|
|
39
|
+
await writeFile(candidate, contents, "utf8");
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
42
|
// best effort
|
|
@@ -46,7 +46,7 @@ export async function writeDevToolsActivePort(userDataDir, port) {
|
|
|
46
46
|
export async function readChromePid(userDataDir) {
|
|
47
47
|
const pidPath = path.join(userDataDir, CHROME_PID_FILENAME);
|
|
48
48
|
try {
|
|
49
|
-
const raw = (await readFile(pidPath,
|
|
49
|
+
const raw = (await readFile(pidPath, "utf8")).trim();
|
|
50
50
|
const pid = Number.parseInt(raw, 10);
|
|
51
51
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
52
52
|
return null;
|
|
@@ -63,7 +63,7 @@ export async function writeChromePid(userDataDir, pid) {
|
|
|
63
63
|
const pidPath = path.join(userDataDir, CHROME_PID_FILENAME);
|
|
64
64
|
try {
|
|
65
65
|
await mkdir(path.dirname(pidPath), { recursive: true });
|
|
66
|
-
await writeFile(pidPath, `${Math.trunc(pid)}\n`,
|
|
66
|
+
await writeFile(pidPath, `${Math.trunc(pid)}\n`, "utf8");
|
|
67
67
|
}
|
|
68
68
|
catch {
|
|
69
69
|
// best effort
|
|
@@ -78,7 +78,10 @@ export function isProcessAlive(pid) {
|
|
|
78
78
|
}
|
|
79
79
|
catch (error) {
|
|
80
80
|
// EPERM means "exists but no permission"; treat as alive.
|
|
81
|
-
if (error &&
|
|
81
|
+
if (error &&
|
|
82
|
+
typeof error === "object" &&
|
|
83
|
+
"code" in error &&
|
|
84
|
+
error.code === "EPERM") {
|
|
82
85
|
return true;
|
|
83
86
|
}
|
|
84
87
|
return false;
|
|
@@ -91,7 +94,7 @@ function parseProfileRunLock(payload) {
|
|
|
91
94
|
const parsed = JSON.parse(payload);
|
|
92
95
|
if (!Number.isFinite(parsed.pid) || parsed.pid <= 0)
|
|
93
96
|
return null;
|
|
94
|
-
if (!parsed.lockId || typeof parsed.lockId !==
|
|
97
|
+
if (!parsed.lockId || typeof parsed.lockId !== "string")
|
|
95
98
|
return null;
|
|
96
99
|
return parsed;
|
|
97
100
|
}
|
|
@@ -104,7 +107,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
104
107
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
105
108
|
return null;
|
|
106
109
|
}
|
|
107
|
-
const pollMs = typeof options.pollMs ===
|
|
110
|
+
const pollMs = typeof options.pollMs === "number" && Number.isFinite(options.pollMs) && options.pollMs > 0
|
|
108
111
|
? options.pollMs
|
|
109
112
|
: 1000;
|
|
110
113
|
const lockPath = path.join(userDataDir, ORACLE_PROFILE_LOCK_FILENAME);
|
|
@@ -120,7 +123,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
120
123
|
sessionId: options.sessionId,
|
|
121
124
|
};
|
|
122
125
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
123
|
-
await writeFile(lockPath, JSON.stringify(payload), { encoding:
|
|
126
|
+
await writeFile(lockPath, JSON.stringify(payload), { encoding: "utf8", flag: "wx" });
|
|
124
127
|
options.logger?.(`Acquired Oracle profile lock at ${lockPath}`);
|
|
125
128
|
return {
|
|
126
129
|
path: lockPath,
|
|
@@ -130,16 +133,16 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
130
133
|
}
|
|
131
134
|
catch (error) {
|
|
132
135
|
const code = error.code;
|
|
133
|
-
if (code !==
|
|
136
|
+
if (code !== "EEXIST") {
|
|
134
137
|
throw error;
|
|
135
138
|
}
|
|
136
|
-
let existing = parseProfileRunLock(await readFile(lockPath,
|
|
139
|
+
let existing = parseProfileRunLock(await readFile(lockPath, "utf8").catch(() => null));
|
|
137
140
|
if (!existing) {
|
|
138
141
|
// Likely partial write / corruption; re-read once, then delete (user preference: delete unreadable lockfiles).
|
|
139
142
|
await delay(200);
|
|
140
|
-
existing = parseProfileRunLock(await readFile(lockPath,
|
|
143
|
+
existing = parseProfileRunLock(await readFile(lockPath, "utf8").catch(() => null));
|
|
141
144
|
if (!existing) {
|
|
142
|
-
options.logger?.(
|
|
145
|
+
options.logger?.("Oracle profile lock unreadable; deleting lockfile.");
|
|
143
146
|
await rm(lockPath, { force: true }).catch(() => undefined);
|
|
144
147
|
continue;
|
|
145
148
|
}
|
|
@@ -163,7 +166,7 @@ export async function acquireProfileRunLock(userDataDir, options) {
|
|
|
163
166
|
}
|
|
164
167
|
export async function releaseProfileRunLock(lockPath, lockId, logger) {
|
|
165
168
|
try {
|
|
166
|
-
const existing = parseProfileRunLock(await readFile(lockPath,
|
|
169
|
+
const existing = parseProfileRunLock(await readFile(lockPath, "utf8").catch(() => null));
|
|
167
170
|
if (!existing || existing.lockId !== lockId) {
|
|
168
171
|
return;
|
|
169
172
|
}
|
|
@@ -174,7 +177,7 @@ export async function releaseProfileRunLock(lockPath, lockId, logger) {
|
|
|
174
177
|
// best effort
|
|
175
178
|
}
|
|
176
179
|
}
|
|
177
|
-
export async function verifyDevToolsReachable({ port, host =
|
|
180
|
+
export async function verifyDevToolsReachable({ port, host = "127.0.0.1", attempts = 3, timeoutMs = 3000, }) {
|
|
178
181
|
const versionUrl = `http://${host}:${port}/json/version`;
|
|
179
182
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
180
183
|
try {
|
|
@@ -196,7 +199,7 @@ export async function verifyDevToolsReachable({ port, host = '127.0.0.1', attemp
|
|
|
196
199
|
return { ok: false, error: message };
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
|
-
return { ok: false, error:
|
|
202
|
+
return { ok: false, error: "unreachable" };
|
|
200
203
|
}
|
|
201
204
|
export async function shouldCleanupManualLoginProfileState(userDataDir, logger, options = {}) {
|
|
202
205
|
if (!options.connectionClosedUnexpectedly) {
|
|
@@ -224,8 +227,8 @@ export async function cleanupStaleProfileState(userDataDir, logger, options = {}
|
|
|
224
227
|
// ignore cleanup errors
|
|
225
228
|
}
|
|
226
229
|
}
|
|
227
|
-
const lockRemovalMode = options.lockRemovalMode ??
|
|
228
|
-
if (lockRemovalMode ===
|
|
230
|
+
const lockRemovalMode = options.lockRemovalMode ?? "never";
|
|
231
|
+
if (lockRemovalMode === "never") {
|
|
229
232
|
return;
|
|
230
233
|
}
|
|
231
234
|
const pid = await readChromePid(userDataDir);
|
|
@@ -239,36 +242,38 @@ export async function cleanupStaleProfileState(userDataDir, logger, options = {}
|
|
|
239
242
|
// Extra safety: if Chrome is running with this profile (but with a different PID, e.g. user relaunched
|
|
240
243
|
// without remote debugging), never delete lock files.
|
|
241
244
|
if (await isChromeUsingUserDataDir(userDataDir)) {
|
|
242
|
-
logger?.(
|
|
245
|
+
logger?.("Detected running Chrome using this profile; skipping profile lock cleanup");
|
|
243
246
|
return;
|
|
244
247
|
}
|
|
245
248
|
const lockFiles = [
|
|
246
|
-
path.join(userDataDir,
|
|
247
|
-
path.join(userDataDir,
|
|
248
|
-
path.join(userDataDir,
|
|
249
|
-
path.join(userDataDir,
|
|
249
|
+
path.join(userDataDir, "lockfile"),
|
|
250
|
+
path.join(userDataDir, "SingletonLock"),
|
|
251
|
+
path.join(userDataDir, "SingletonSocket"),
|
|
252
|
+
path.join(userDataDir, "SingletonCookie"),
|
|
250
253
|
];
|
|
251
254
|
for (const lock of lockFiles) {
|
|
252
255
|
await rm(lock, { force: true }).catch(() => undefined);
|
|
253
256
|
}
|
|
254
|
-
logger?.(
|
|
257
|
+
logger?.("Cleaned up stale Chrome profile locks");
|
|
255
258
|
}
|
|
256
259
|
async function isChromeUsingUserDataDir(userDataDir) {
|
|
257
|
-
if (process.platform ===
|
|
260
|
+
if (process.platform === "win32") {
|
|
258
261
|
// On Windows, lockfiles are typically held open and removal should fail anyway; avoid expensive process scans.
|
|
259
262
|
return false;
|
|
260
263
|
}
|
|
261
264
|
try {
|
|
262
|
-
const { stdout } = await execFileAsync(
|
|
263
|
-
|
|
265
|
+
const { stdout } = await execFileAsync("ps", ["-ax", "-o", "command="], {
|
|
266
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
267
|
+
});
|
|
268
|
+
const lines = String(stdout ?? "").split("\n");
|
|
264
269
|
const needle = userDataDir;
|
|
265
270
|
for (const line of lines) {
|
|
266
271
|
if (!line)
|
|
267
272
|
continue;
|
|
268
273
|
const lower = line.toLowerCase();
|
|
269
|
-
if (!lower.includes(
|
|
274
|
+
if (!lower.includes("chrome") && !lower.includes("chromium"))
|
|
270
275
|
continue;
|
|
271
|
-
if (line.includes(needle) && lower.includes(
|
|
276
|
+
if (line.includes(needle) && lower.includes("user-data-dir")) {
|
|
272
277
|
return true;
|
|
273
278
|
}
|
|
274
279
|
}
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import { readFiles, createFileSections, MODEL_CONFIGS, TOKENIZER_OPTIONS, formatFileSection, } from
|
|
5
|
-
import { isKnownModel } from
|
|
6
|
-
import { buildPromptMarkdown } from
|
|
7
|
-
import { buildAttachmentPlan } from
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readFiles, createFileSections, MODEL_CONFIGS, TOKENIZER_OPTIONS, formatFileSection, } from "../oracle.js";
|
|
5
|
+
import { isKnownModel } from "../oracle/modelResolver.js";
|
|
6
|
+
import { buildPromptMarkdown } from "../oracle/promptAssembly.js";
|
|
7
|
+
import { buildAttachmentPlan } from "./policies.js";
|
|
8
8
|
const DEFAULT_BROWSER_INLINE_CHAR_BUDGET = 60_000;
|
|
9
9
|
const MEDIA_EXTENSIONS = new Set([
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
".mp4",
|
|
11
|
+
".mov",
|
|
12
|
+
".avi",
|
|
13
|
+
".mkv",
|
|
14
|
+
".webm",
|
|
15
|
+
".m4v",
|
|
16
|
+
".mp3",
|
|
17
|
+
".wav",
|
|
18
|
+
".aac",
|
|
19
|
+
".flac",
|
|
20
|
+
".ogg",
|
|
21
|
+
".m4a",
|
|
22
|
+
".jpg",
|
|
23
|
+
".jpeg",
|
|
24
|
+
".png",
|
|
25
|
+
".gif",
|
|
26
|
+
".webp",
|
|
27
|
+
".bmp",
|
|
28
|
+
".svg",
|
|
29
|
+
".heic",
|
|
30
|
+
".heif",
|
|
31
|
+
".pdf",
|
|
14
32
|
]);
|
|
15
33
|
export function isMediaFile(filePath) {
|
|
16
34
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -32,14 +50,14 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
32
50
|
};
|
|
33
51
|
}));
|
|
34
52
|
const files = await readFilesFn(textFilePaths, { cwd });
|
|
35
|
-
const basePrompt = (runOptions.prompt ??
|
|
53
|
+
const basePrompt = (runOptions.prompt ?? "").trim();
|
|
36
54
|
const userPrompt = basePrompt;
|
|
37
|
-
const systemPrompt = runOptions.system?.trim() ||
|
|
55
|
+
const systemPrompt = runOptions.system?.trim() || "";
|
|
38
56
|
const sections = createFileSections(files, cwd);
|
|
39
57
|
const markdown = buildPromptMarkdown(systemPrompt, userPrompt, sections);
|
|
40
58
|
const attachmentsPolicy = runOptions.browserInlineFiles
|
|
41
|
-
?
|
|
42
|
-
: runOptions.browserAttachments ??
|
|
59
|
+
? "never"
|
|
60
|
+
: (runOptions.browserAttachments ?? "auto");
|
|
43
61
|
const bundleRequested = Boolean(runOptions.browserBundleFiles);
|
|
44
62
|
const inlinePlan = buildAttachmentPlan(sections, { inlineFiles: true, bundleRequested });
|
|
45
63
|
const uploadPlan = buildAttachmentPlan(sections, { inlineFiles: false, bundleRequested });
|
|
@@ -48,10 +66,13 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
48
66
|
baseComposerSections.push(systemPrompt);
|
|
49
67
|
if (userPrompt)
|
|
50
68
|
baseComposerSections.push(userPrompt);
|
|
51
|
-
const inlineComposerText = [...baseComposerSections, inlinePlan.inlineBlock]
|
|
52
|
-
|
|
69
|
+
const inlineComposerText = [...baseComposerSections, inlinePlan.inlineBlock]
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.join("\n\n")
|
|
72
|
+
.trim();
|
|
73
|
+
const selectedPlan = attachmentsPolicy === "always"
|
|
53
74
|
? uploadPlan
|
|
54
|
-
: attachmentsPolicy ===
|
|
75
|
+
: attachmentsPolicy === "never"
|
|
55
76
|
? inlinePlan
|
|
56
77
|
: inlineComposerText.length <= DEFAULT_BROWSER_INLINE_CHAR_BUDGET || sections.length === 0
|
|
57
78
|
? inlinePlan
|
|
@@ -60,73 +81,82 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
60
81
|
? [...baseComposerSections, selectedPlan.inlineBlock]
|
|
61
82
|
: baseComposerSections)
|
|
62
83
|
.filter(Boolean)
|
|
63
|
-
.join(
|
|
84
|
+
.join("\n\n")
|
|
64
85
|
.trim();
|
|
65
86
|
const attachments = [...selectedPlan.attachments, ...mediaAttachments];
|
|
66
87
|
const shouldBundle = selectedPlan.shouldBundle;
|
|
67
88
|
let bundleText = null;
|
|
68
89
|
let bundled = null;
|
|
69
90
|
if (shouldBundle) {
|
|
70
|
-
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(),
|
|
71
|
-
const bundlePath = path.join(bundleDir,
|
|
91
|
+
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), "oracle-browser-bundle-"));
|
|
92
|
+
const bundlePath = path.join(bundleDir, "attachments-bundle.txt");
|
|
72
93
|
const bundleLines = [];
|
|
73
94
|
sections.forEach((section) => {
|
|
74
95
|
bundleLines.push(formatFileSection(section.displayPath, section.content).trimEnd());
|
|
75
|
-
bundleLines.push(
|
|
96
|
+
bundleLines.push("");
|
|
76
97
|
});
|
|
77
|
-
bundleText = `${bundleLines
|
|
78
|
-
|
|
98
|
+
bundleText = `${bundleLines
|
|
99
|
+
.join("\n")
|
|
100
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
101
|
+
.trimEnd()}\n`;
|
|
102
|
+
await fs.writeFile(bundlePath, bundleText, "utf8");
|
|
79
103
|
attachments.length = 0;
|
|
80
104
|
attachments.push({
|
|
81
105
|
path: bundlePath,
|
|
82
106
|
displayPath: bundlePath,
|
|
83
|
-
sizeBytes: Buffer.byteLength(bundleText,
|
|
107
|
+
sizeBytes: Buffer.byteLength(bundleText, "utf8"),
|
|
84
108
|
});
|
|
85
109
|
attachments.push(...mediaAttachments);
|
|
86
110
|
bundled = { originalCount: sections.length, bundlePath };
|
|
87
111
|
}
|
|
88
112
|
const inlineFileCount = selectedPlan.inlineFileCount;
|
|
89
|
-
const modelConfig = isKnownModel(runOptions.model)
|
|
113
|
+
const modelConfig = isKnownModel(runOptions.model)
|
|
114
|
+
? MODEL_CONFIGS[runOptions.model]
|
|
115
|
+
: MODEL_CONFIGS["gpt-5.1"];
|
|
90
116
|
const tokenizer = deps.tokenizeImpl ?? modelConfig.tokenizer;
|
|
91
117
|
const tokenizerUserContent = inlineFileCount > 0 && selectedPlan.inlineBlock
|
|
92
|
-
? [userPrompt, selectedPlan.inlineBlock]
|
|
118
|
+
? [userPrompt, selectedPlan.inlineBlock]
|
|
119
|
+
.filter((value) => Boolean(value?.trim()))
|
|
120
|
+
.join("\n\n")
|
|
121
|
+
.trim()
|
|
93
122
|
: userPrompt;
|
|
94
123
|
const tokenizerMessages = [
|
|
95
|
-
systemPrompt ? { role:
|
|
96
|
-
tokenizerUserContent ? { role:
|
|
124
|
+
systemPrompt ? { role: "system", content: systemPrompt } : null,
|
|
125
|
+
tokenizerUserContent ? { role: "user", content: tokenizerUserContent } : null,
|
|
97
126
|
].filter(Boolean);
|
|
98
|
-
let estimatedInputTokens = tokenizer(tokenizerMessages.length > 0
|
|
99
|
-
? tokenizerMessages
|
|
100
|
-
: [{ role: 'user', content: '' }], TOKENIZER_OPTIONS);
|
|
127
|
+
let estimatedInputTokens = tokenizer(tokenizerMessages.length > 0 ? tokenizerMessages : [{ role: "user", content: "" }], TOKENIZER_OPTIONS);
|
|
101
128
|
const tokenEstimateIncludesInlineFiles = inlineFileCount > 0 && Boolean(selectedPlan.inlineBlock);
|
|
102
129
|
if (!tokenEstimateIncludesInlineFiles && sections.length > 0) {
|
|
103
130
|
const attachmentText = bundleText ??
|
|
104
131
|
sections
|
|
105
132
|
.map((section) => formatFileSection(section.displayPath, section.content).trimEnd())
|
|
106
|
-
.join(
|
|
107
|
-
const attachmentTokens = tokenizer([{ role:
|
|
133
|
+
.join("\n\n");
|
|
134
|
+
const attachmentTokens = tokenizer([{ role: "user", content: attachmentText }], TOKENIZER_OPTIONS);
|
|
108
135
|
estimatedInputTokens += attachmentTokens;
|
|
109
136
|
}
|
|
110
137
|
let fallback = null;
|
|
111
|
-
if (attachmentsPolicy ===
|
|
112
|
-
const fallbackComposerText = baseComposerSections.join(
|
|
138
|
+
if (attachmentsPolicy === "auto" && selectedPlan.mode === "inline" && sections.length > 0) {
|
|
139
|
+
const fallbackComposerText = baseComposerSections.join("\n\n").trim();
|
|
113
140
|
const fallbackAttachments = [...uploadPlan.attachments, ...mediaAttachments];
|
|
114
141
|
let fallbackBundled = null;
|
|
115
142
|
if (uploadPlan.shouldBundle) {
|
|
116
|
-
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(),
|
|
117
|
-
const bundlePath = path.join(bundleDir,
|
|
143
|
+
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), "oracle-browser-bundle-"));
|
|
144
|
+
const bundlePath = path.join(bundleDir, "attachments-bundle.txt");
|
|
118
145
|
const bundleLines = [];
|
|
119
146
|
sections.forEach((section) => {
|
|
120
147
|
bundleLines.push(formatFileSection(section.displayPath, section.content).trimEnd());
|
|
121
|
-
bundleLines.push(
|
|
148
|
+
bundleLines.push("");
|
|
122
149
|
});
|
|
123
|
-
const fallbackBundleText = `${bundleLines
|
|
124
|
-
|
|
150
|
+
const fallbackBundleText = `${bundleLines
|
|
151
|
+
.join("\n")
|
|
152
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
153
|
+
.trimEnd()}\n`;
|
|
154
|
+
await fs.writeFile(bundlePath, fallbackBundleText, "utf8");
|
|
125
155
|
fallbackAttachments.length = 0;
|
|
126
156
|
fallbackAttachments.push({
|
|
127
157
|
path: bundlePath,
|
|
128
158
|
displayPath: bundlePath,
|
|
129
|
-
sizeBytes: Buffer.byteLength(fallbackBundleText,
|
|
159
|
+
sizeBytes: Buffer.byteLength(fallbackBundleText, "utf8"),
|
|
130
160
|
});
|
|
131
161
|
fallbackAttachments.push(...mediaAttachments);
|
|
132
162
|
fallbackBundled = { originalCount: sections.length, bundlePath };
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { formatBytes } from
|
|
1
|
+
import { formatBytes } from "./utils.js";
|
|
2
2
|
export function buildTokenEstimateSuffix(artifacts) {
|
|
3
3
|
if (artifacts.tokenEstimateIncludesInlineFiles && artifacts.inlineFileCount > 0) {
|
|
4
4
|
const count = artifacts.inlineFileCount;
|
|
5
|
-
const plural = count === 1 ?
|
|
5
|
+
const plural = count === 1 ? "" : "s";
|
|
6
6
|
return ` (includes ${count} inline file${plural})`;
|
|
7
7
|
}
|
|
8
8
|
if (artifacts.attachments.length > 0) {
|
|
9
9
|
const count = artifacts.attachments.length;
|
|
10
|
-
const plural = count === 1 ?
|
|
10
|
+
const plural = count === 1 ? "" : "s";
|
|
11
11
|
return ` (prompt only; ${count} attachment${plural} excluded)`;
|
|
12
12
|
}
|
|
13
|
-
return
|
|
13
|
+
return "";
|
|
14
14
|
}
|
|
15
15
|
export function formatAttachmentLabel(attachment) {
|
|
16
|
-
if (typeof attachment.sizeBytes !==
|
|
16
|
+
if (typeof attachment.sizeBytes !== "number" || Number.isNaN(attachment.sizeBytes)) {
|
|
17
17
|
return attachment.displayPath;
|
|
18
18
|
}
|
|
19
19
|
return `${attachment.displayPath} (${formatBytes(attachment.sizeBytes)})`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function runProviderSubmissionFlow(adapter, ctx) {
|
|
2
|
+
await adapter.waitForUi(ctx);
|
|
3
|
+
if (adapter.selectMode) {
|
|
4
|
+
await adapter.selectMode(ctx);
|
|
5
|
+
}
|
|
6
|
+
await adapter.typePrompt(ctx);
|
|
7
|
+
await adapter.submitPrompt(ctx);
|
|
8
|
+
}
|
|
9
|
+
export async function runProviderDomFlow(adapter, ctx) {
|
|
10
|
+
await runProviderSubmissionFlow(adapter, ctx);
|
|
11
|
+
const response = await adapter.waitForResponse(ctx);
|
|
12
|
+
const thoughts = adapter.extractThoughts ? await adapter.extractThoughts(ctx) : null;
|
|
13
|
+
return { ...response, thoughts };
|
|
14
|
+
}
|
|
15
|
+
export function joinSelectors(selectors) {
|
|
16
|
+
return selectors.join(", ");
|
|
17
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ensurePromptReady } from "../actions/navigation.js";
|
|
2
|
+
import { submitPrompt } from "../actions/promptComposer.js";
|
|
3
|
+
import { waitForAssistantResponse } from "../actions/assistantResponse.js";
|
|
4
|
+
function requireState(ctx) {
|
|
5
|
+
const state = ctx.state;
|
|
6
|
+
if (!state?.runtime || !state?.input || !state?.logger) {
|
|
7
|
+
throw new Error("chatgptDomProvider requires runtime/input/logger in context.state.");
|
|
8
|
+
}
|
|
9
|
+
return state;
|
|
10
|
+
}
|
|
11
|
+
async function waitForUi(ctx) {
|
|
12
|
+
const state = requireState(ctx);
|
|
13
|
+
await ensurePromptReady(state.runtime, state.inputTimeoutMs ?? 30_000, state.logger);
|
|
14
|
+
}
|
|
15
|
+
async function typePrompt(_ctx) {
|
|
16
|
+
// submitPrompt() handles typing + send for ChatGPT.
|
|
17
|
+
}
|
|
18
|
+
async function submitPromptViaAdapter(ctx) {
|
|
19
|
+
const state = requireState(ctx);
|
|
20
|
+
const committedTurns = await submitPrompt({
|
|
21
|
+
runtime: state.runtime,
|
|
22
|
+
input: state.input,
|
|
23
|
+
attachmentNames: state.attachmentNames ?? [],
|
|
24
|
+
baselineTurns: state.baselineTurns ?? undefined,
|
|
25
|
+
inputTimeoutMs: state.inputTimeoutMs ?? undefined,
|
|
26
|
+
}, ctx.prompt, state.logger);
|
|
27
|
+
state.committedTurns =
|
|
28
|
+
typeof committedTurns === "number" && Number.isFinite(committedTurns) ? committedTurns : null;
|
|
29
|
+
if (state.committedTurns != null &&
|
|
30
|
+
(state.baselineTurns == null || state.committedTurns > state.baselineTurns)) {
|
|
31
|
+
state.baselineTurns = Math.max(0, state.committedTurns - 1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function waitForResponse(ctx) {
|
|
35
|
+
const state = requireState(ctx);
|
|
36
|
+
const answer = await waitForAssistantResponse(state.runtime, state.timeoutMs, state.logger, state.baselineTurns ?? undefined);
|
|
37
|
+
return {
|
|
38
|
+
text: answer.text,
|
|
39
|
+
html: answer.html,
|
|
40
|
+
meta: answer.meta,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const chatgptDomProvider = {
|
|
44
|
+
providerName: "chatgpt-web",
|
|
45
|
+
waitForUi,
|
|
46
|
+
typePrompt,
|
|
47
|
+
submitPrompt: submitPromptViaAdapter,
|
|
48
|
+
waitForResponse,
|
|
49
|
+
};
|