@steipete/oracle 0.9.0 → 0.11.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 +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- 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/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- 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 +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- 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 +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- 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 +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- 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 +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- 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 +16 -13
- package/dist/src/oracle/run.js +160 -135
- 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/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- 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 +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- 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 → src/projectSources}/types.js +0 -0
|
@@ -1,59 +1,86 @@
|
|
|
1
|
-
import CDP from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import { mkdtemp, mkdir, rm } from
|
|
5
|
-
import { waitForAssistantResponse, captureAssistantMarkdown, navigateToChatGPT, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, } from
|
|
6
|
-
import { launchChrome, connectToChrome, hideChromeWindow } from
|
|
7
|
-
import { resolveBrowserConfig } from
|
|
8
|
-
import { syncCookies } from
|
|
9
|
-
import { CHATGPT_URL } from
|
|
10
|
-
import { cleanupStaleProfileState } from
|
|
11
|
-
import {
|
|
1
|
+
import CDP from "chrome-remote-interface";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
|
5
|
+
import { waitForAssistantResponse, captureAssistantMarkdown, navigateToChatGPT, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, } from "./pageActions.js";
|
|
6
|
+
import { launchChrome, connectToChrome, hideChromeWindow, connectToRemoteChromeTarget, listRemoteChromeTargets, } from "./chromeLifecycle.js";
|
|
7
|
+
import { resolveBrowserConfig } from "./config.js";
|
|
8
|
+
import { syncCookies } from "./cookies.js";
|
|
9
|
+
import { CHATGPT_URL, CONVERSATION_TURN_SELECTOR } from "./constants.js";
|
|
10
|
+
import { cleanupStaleProfileState } from "./profileState.js";
|
|
11
|
+
import { readDevToolsActivePortInfo } from "./detect.js";
|
|
12
|
+
import { pickTarget, extractConversationIdFromUrl, buildConversationUrl, withTimeout, openConversationFromSidebar, openConversationFromSidebarWithRetry, waitForLocationChange, readConversationTurnIndex, buildPromptEchoMatcher, recoverPromptEcho, alignPromptEchoMarkdown, } from "./reattachHelpers.js";
|
|
13
|
+
import { waitForDeepResearchCompletion } from "./actions/deepResearch.js";
|
|
12
14
|
export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
13
15
|
const recoverSession = deps.recoverSession ??
|
|
14
16
|
(async (runtimeMeta, configMeta) => resumeBrowserSessionViaNewChrome(runtimeMeta, configMeta, logger, deps));
|
|
15
|
-
if (!runtime.chromePort) {
|
|
16
|
-
logger(
|
|
17
|
+
if (!runtime.chromePort && !runtime.chromeBrowserWSEndpoint) {
|
|
18
|
+
logger("No running Chrome detected; reopening browser to locate the session.");
|
|
17
19
|
return recoverSession(runtime, config);
|
|
18
20
|
}
|
|
19
|
-
const host = runtime.chromeHost ?? '127.0.0.1';
|
|
20
21
|
try {
|
|
22
|
+
const liveRuntime = (await refreshAttachRuntime(runtime).catch(() => runtime)) ?? runtime;
|
|
23
|
+
const host = liveRuntime.chromeHost ?? "127.0.0.1";
|
|
24
|
+
const port = liveRuntime.chromePort ?? inferPortFromBrowserWSEndpoint(liveRuntime.chromeBrowserWSEndpoint);
|
|
25
|
+
const browserWSEndpoint = liveRuntime.chromeBrowserWSEndpoint ?? undefined;
|
|
21
26
|
const listTargets = deps.listTargets ??
|
|
22
|
-
(async () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
(async () => (await listRemoteChromeTargets({
|
|
28
|
+
host,
|
|
29
|
+
port: port ?? 9222,
|
|
30
|
+
browserWSEndpoint,
|
|
31
|
+
})));
|
|
27
32
|
const targetList = (await listTargets());
|
|
28
|
-
const target = pickTarget(targetList,
|
|
29
|
-
const
|
|
30
|
-
host,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const target = pickTarget(targetList, liveRuntime);
|
|
34
|
+
const connection = browserWSEndpoint && !deps.connect
|
|
35
|
+
? await connectToRemoteChromeTarget(host, port ?? 9222, logger, {
|
|
36
|
+
browserWSEndpoint,
|
|
37
|
+
targetId: target?.targetId ?? target?.id,
|
|
38
|
+
closeTargetOnDispose: false,
|
|
39
|
+
})
|
|
40
|
+
: {
|
|
41
|
+
client: (await (deps.connect ?? ((options) => CDP(options)))(browserWSEndpoint
|
|
42
|
+
? {
|
|
43
|
+
target: browserWSEndpoint,
|
|
44
|
+
local: true,
|
|
45
|
+
targetId: target?.targetId ?? target?.id,
|
|
46
|
+
}
|
|
47
|
+
: {
|
|
48
|
+
host,
|
|
49
|
+
port,
|
|
50
|
+
target: target?.targetId ?? target?.id,
|
|
51
|
+
})),
|
|
52
|
+
close: async () => undefined,
|
|
53
|
+
};
|
|
54
|
+
const client = connection.client;
|
|
55
|
+
const { Runtime, DOM, Page } = client;
|
|
35
56
|
if (Runtime?.enable) {
|
|
36
57
|
await Runtime.enable();
|
|
37
58
|
}
|
|
38
|
-
if (DOM && typeof DOM.enable ===
|
|
59
|
+
if (DOM && typeof DOM.enable === "function") {
|
|
39
60
|
await DOM.enable();
|
|
40
61
|
}
|
|
62
|
+
if (Page && typeof Page.enable === "function") {
|
|
63
|
+
await Page.enable();
|
|
64
|
+
}
|
|
41
65
|
const ensureConversationOpen = async () => {
|
|
42
|
-
const { result } = await Runtime.evaluate({
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
const { result } = await Runtime.evaluate({
|
|
67
|
+
expression: "location.href",
|
|
68
|
+
returnByValue: true,
|
|
69
|
+
});
|
|
70
|
+
const href = typeof result?.value === "string" ? result.value : "";
|
|
71
|
+
if (href.includes("/c/")) {
|
|
45
72
|
const currentId = extractConversationIdFromUrl(href);
|
|
46
73
|
if (!runtime.conversationId || (currentId && currentId === runtime.conversationId)) {
|
|
47
74
|
return;
|
|
48
75
|
}
|
|
49
76
|
}
|
|
50
77
|
const opened = await openConversationFromSidebarWithRetry(Runtime, {
|
|
51
|
-
conversationId: runtime.conversationId ?? extractConversationIdFromUrl(runtime.tabUrl ??
|
|
78
|
+
conversationId: runtime.conversationId ?? extractConversationIdFromUrl(runtime.tabUrl ?? ""),
|
|
52
79
|
preferProjects: true,
|
|
53
80
|
promptPreview: deps.promptPreview,
|
|
54
81
|
}, 15_000);
|
|
55
82
|
if (!opened) {
|
|
56
|
-
throw new Error(
|
|
83
|
+
throw new Error("Unable to locate prior ChatGPT conversation in sidebar.");
|
|
57
84
|
}
|
|
58
85
|
await waitForLocationChange(Runtime, 15_000);
|
|
59
86
|
};
|
|
@@ -61,22 +88,25 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
61
88
|
const captureMarkdown = deps.captureAssistantMarkdown ?? captureAssistantMarkdown;
|
|
62
89
|
const timeoutMs = config?.timeoutMs ?? 120_000;
|
|
63
90
|
const pingTimeoutMs = Math.min(5_000, Math.max(1_500, Math.floor(timeoutMs * 0.05)));
|
|
64
|
-
await withTimeout(Runtime.evaluate({ expression:
|
|
91
|
+
await withTimeout(Runtime.evaluate({ expression: "1+1", returnByValue: true }), pingTimeoutMs, "Reattach target did not respond");
|
|
65
92
|
await ensureConversationOpen();
|
|
66
|
-
const minTurnIndex = await
|
|
93
|
+
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
94
|
+
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
95
|
+
if (config?.researchMode === "deep") {
|
|
96
|
+
const waitForDeepResearch = deps.waitForDeepResearchCompletion ?? waitForDeepResearchCompletion;
|
|
97
|
+
const researchResult = await withTimeout(waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client), timeoutMs + 5_000, "Reattach Deep Research response timed out");
|
|
98
|
+
await connection.close().catch(() => undefined);
|
|
99
|
+
return {
|
|
100
|
+
answerText: researchResult.text,
|
|
101
|
+
answerMarkdown: researchResult.text,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
67
104
|
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
68
|
-
const answer = await withTimeout(waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined), timeoutMs + 5_000,
|
|
105
|
+
const answer = await withTimeout(waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined), timeoutMs + 5_000, "Reattach response timed out");
|
|
69
106
|
const recovered = await recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs);
|
|
70
|
-
const markdown = (await withTimeout(captureMarkdown(Runtime, recovered.meta, logger), 15_000,
|
|
107
|
+
const markdown = (await withTimeout(captureMarkdown(Runtime, recovered.meta, logger), 15_000, "Reattach markdown capture timed out")) ?? recovered.text;
|
|
71
108
|
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
await client.close();
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// ignore
|
|
78
|
-
}
|
|
79
|
-
}
|
|
109
|
+
await connection.close().catch(() => undefined);
|
|
80
110
|
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
81
111
|
}
|
|
82
112
|
catch (error) {
|
|
@@ -85,23 +115,57 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
85
115
|
return recoverSession(runtime, config);
|
|
86
116
|
}
|
|
87
117
|
}
|
|
118
|
+
async function refreshAttachRuntime(runtime) {
|
|
119
|
+
if (!runtime.chromeProfileRoot) {
|
|
120
|
+
return runtime;
|
|
121
|
+
}
|
|
122
|
+
const host = runtime.chromeHost ?? "127.0.0.1";
|
|
123
|
+
const activePort = await readDevToolsActivePortInfo(runtime.chromeProfileRoot, {
|
|
124
|
+
host,
|
|
125
|
+
});
|
|
126
|
+
if (!activePort) {
|
|
127
|
+
return runtime;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
...runtime,
|
|
131
|
+
chromeHost: host,
|
|
132
|
+
chromePort: activePort.port,
|
|
133
|
+
chromeBrowserWSEndpoint: activePort.browserWSEndpoint,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function inferPortFromBrowserWSEndpoint(browserWSEndpoint) {
|
|
137
|
+
if (!browserWSEndpoint) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const parsed = new URL(browserWSEndpoint);
|
|
142
|
+
const port = Number.parseInt(parsed.port, 10);
|
|
143
|
+
if (Number.isFinite(port) && port > 0) {
|
|
144
|
+
return port;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// ignore malformed ws endpoints and fall back to caller defaults
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
88
152
|
async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
89
153
|
const resolved = resolveBrowserConfig(config ?? {});
|
|
90
154
|
const manualLogin = Boolean(resolved.manualLogin);
|
|
91
155
|
const userDataDir = manualLogin
|
|
92
|
-
? resolved.manualLoginProfileDir ?? path.join(os.homedir(),
|
|
93
|
-
: await mkdtemp(path.join(os.tmpdir(),
|
|
156
|
+
? (resolved.manualLoginProfileDir ?? path.join(os.homedir(), ".oracle", "browser-profile"))
|
|
157
|
+
: await mkdtemp(path.join(os.tmpdir(), "oracle-reattach-"));
|
|
94
158
|
if (manualLogin) {
|
|
95
159
|
await mkdir(userDataDir, { recursive: true });
|
|
96
160
|
}
|
|
97
161
|
const chrome = await launchChrome(resolved, userDataDir, logger);
|
|
98
|
-
const chromeHost = chrome.host ??
|
|
162
|
+
const chromeHost = chrome.host ?? "127.0.0.1";
|
|
99
163
|
const client = await connectToChrome(chrome.port, logger, chromeHost);
|
|
100
164
|
const { Network, Page, Runtime, DOM } = client;
|
|
101
165
|
if (Runtime?.enable) {
|
|
102
166
|
await Runtime.enable();
|
|
103
167
|
}
|
|
104
|
-
if (DOM && typeof DOM.enable ===
|
|
168
|
+
if (DOM && typeof DOM.enable === "function") {
|
|
105
169
|
await DOM.enable();
|
|
106
170
|
}
|
|
107
171
|
if (!resolved.headless && resolved.hideWindow) {
|
|
@@ -134,48 +198,88 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
134
198
|
}
|
|
135
199
|
else {
|
|
136
200
|
const opened = await openConversationFromSidebarWithRetry(Runtime, {
|
|
137
|
-
conversationId: runtime.conversationId ?? extractConversationIdFromUrl(runtime.tabUrl ??
|
|
201
|
+
conversationId: runtime.conversationId ?? extractConversationIdFromUrl(runtime.tabUrl ?? ""),
|
|
138
202
|
preferProjects: resolved.url !== CHATGPT_URL ||
|
|
139
|
-
Boolean(runtime.tabUrl && (/\/g\//.test(runtime.tabUrl) || runtime.tabUrl.includes(
|
|
203
|
+
Boolean(runtime.tabUrl && (/\/g\//.test(runtime.tabUrl) || runtime.tabUrl.includes("/project"))),
|
|
140
204
|
promptPreview: deps.promptPreview,
|
|
141
205
|
}, 15_000);
|
|
142
206
|
if (!opened) {
|
|
143
|
-
throw new Error(
|
|
207
|
+
throw new Error("Unable to locate prior ChatGPT conversation in sidebar.");
|
|
144
208
|
}
|
|
145
209
|
await waitForLocationChange(Runtime, 15_000);
|
|
146
210
|
}
|
|
147
211
|
const waitForResponse = deps.waitForAssistantResponse ?? waitForAssistantResponse;
|
|
148
212
|
const captureMarkdown = deps.captureAssistantMarkdown ?? captureAssistantMarkdown;
|
|
149
213
|
const timeoutMs = resolved.timeoutMs ?? 120_000;
|
|
150
|
-
const
|
|
214
|
+
const cleanup = async () => {
|
|
215
|
+
if (client && typeof client.close === "function") {
|
|
216
|
+
try {
|
|
217
|
+
await client.close();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// ignore
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!resolved.keepBrowser) {
|
|
224
|
+
try {
|
|
225
|
+
await chrome.kill();
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// ignore
|
|
229
|
+
}
|
|
230
|
+
if (manualLogin) {
|
|
231
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const minTurnIndex = (await readPromptPreviewTurnIndex(Runtime, deps.promptPreview)) ??
|
|
239
|
+
(deps.promptPreview ? null : await readConversationTurnIndex(Runtime, logger));
|
|
240
|
+
if (resolved.researchMode === "deep") {
|
|
241
|
+
const waitForDeepResearch = deps.waitForDeepResearchCompletion ?? waitForDeepResearchCompletion;
|
|
242
|
+
const researchResult = await waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client);
|
|
243
|
+
await cleanup();
|
|
244
|
+
return {
|
|
245
|
+
answerText: researchResult.text,
|
|
246
|
+
answerMarkdown: researchResult.text,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
151
249
|
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
152
250
|
const answer = await waitForResponse(Runtime, timeoutMs, logger, minTurnIndex ?? undefined);
|
|
153
251
|
const recovered = await recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs);
|
|
154
252
|
const markdown = (await captureMarkdown(Runtime, recovered.meta, logger)) ?? recovered.text;
|
|
155
253
|
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
254
|
+
await cleanup();
|
|
255
|
+
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
256
|
+
}
|
|
257
|
+
async function readPromptPreviewTurnIndex(Runtime, promptPreview) {
|
|
258
|
+
const preview = promptPreview?.trim();
|
|
259
|
+
if (!preview) {
|
|
260
|
+
return null;
|
|
163
261
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
262
|
+
const { result } = await Runtime.evaluate({
|
|
263
|
+
expression: `(() => {
|
|
264
|
+
const needle = ${JSON.stringify(preview.toLowerCase().replace(/\s+/g, " ").slice(0, 120))};
|
|
265
|
+
if (!needle) return null;
|
|
266
|
+
const normalize = (value) => String(value || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
267
|
+
const turns = Array.from(document.querySelectorAll(${JSON.stringify(CONVERSATION_TURN_SELECTOR)}));
|
|
268
|
+
let matched = null;
|
|
269
|
+
for (const [index, node] of turns.entries()) {
|
|
270
|
+
const attr = (node.getAttribute('data-message-author-role') || node.getAttribute('data-turn') || node.dataset?.turn || '').toLowerCase();
|
|
271
|
+
const isUser = attr === 'user' || Boolean(node.querySelector('[data-message-author-role="user"]'));
|
|
272
|
+
if (!isUser) continue;
|
|
273
|
+
const text = normalize(node.innerText || node.textContent || '');
|
|
274
|
+
if (text.length > 0 && (text.includes(needle) || needle.includes(text.slice(0, needle.length)))) {
|
|
275
|
+
matched = index;
|
|
167
276
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
else {
|
|
175
|
-
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
277
|
+
}
|
|
278
|
+
return matched;
|
|
279
|
+
})()`,
|
|
280
|
+
returnByValue: true,
|
|
281
|
+
});
|
|
282
|
+
return typeof result?.value === "number" ? result.value : null;
|
|
179
283
|
}
|
|
180
284
|
// biome-ignore lint/style/useNamingConvention: test-only export used in vitest suite
|
|
181
285
|
export const __test__ = {
|
|
@@ -183,4 +287,5 @@ export const __test__ = {
|
|
|
183
287
|
extractConversationIdFromUrl,
|
|
184
288
|
buildConversationUrl,
|
|
185
289
|
openConversationFromSidebar,
|
|
290
|
+
readPromptPreviewTurnIndex,
|
|
186
291
|
};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { CONVERSATION_TURN_SELECTOR } from
|
|
2
|
-
import { delay } from
|
|
3
|
-
import { readAssistantSnapshot } from
|
|
1
|
+
import { CONVERSATION_TURN_SELECTOR } from "./constants.js";
|
|
2
|
+
import { delay } from "./utils.js";
|
|
3
|
+
import { readAssistantSnapshot } from "./pageActions.js";
|
|
4
4
|
export function pickTarget(targets, runtime) {
|
|
5
5
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
6
6
|
return undefined;
|
|
7
7
|
}
|
|
8
8
|
if (runtime.chromeTargetId) {
|
|
9
|
-
const byId = targets.find((t) => t.targetId === runtime.chromeTargetId);
|
|
9
|
+
const byId = targets.find((t) => (t.targetId ?? t.id) === runtime.chromeTargetId);
|
|
10
10
|
if (byId)
|
|
11
11
|
return byId;
|
|
12
12
|
}
|
|
13
13
|
if (runtime.tabUrl) {
|
|
14
14
|
const byUrl = targets.find((t) => t.url?.startsWith(runtime.tabUrl)) ||
|
|
15
|
-
targets.find((t) => runtime.tabUrl.startsWith(t.url ||
|
|
15
|
+
targets.find((t) => runtime.tabUrl.startsWith(t.url || ""));
|
|
16
16
|
if (byUrl)
|
|
17
17
|
return byUrl;
|
|
18
18
|
}
|
|
19
|
-
return targets.find((t) => t.type ===
|
|
19
|
+
return targets.find((t) => t.type === "page") ?? targets[0];
|
|
20
20
|
}
|
|
21
21
|
export function extractConversationIdFromUrl(url) {
|
|
22
22
|
if (!url)
|
|
@@ -26,7 +26,7 @@ export function extractConversationIdFromUrl(url) {
|
|
|
26
26
|
}
|
|
27
27
|
export function buildConversationUrl(runtime, baseUrl) {
|
|
28
28
|
if (runtime.tabUrl) {
|
|
29
|
-
if (runtime.tabUrl.includes(
|
|
29
|
+
if (runtime.tabUrl.includes("/c/")) {
|
|
30
30
|
return runtime.tabUrl;
|
|
31
31
|
}
|
|
32
32
|
return null;
|
|
@@ -37,8 +37,8 @@ export function buildConversationUrl(runtime, baseUrl) {
|
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
39
39
|
const base = new URL(baseUrl);
|
|
40
|
-
const pathRoot = base.pathname.replace(/\/$/,
|
|
41
|
-
const prefix = pathRoot ===
|
|
40
|
+
const pathRoot = base.pathname.replace(/\/$/, "");
|
|
41
|
+
const prefix = pathRoot === "/" ? "" : pathRoot;
|
|
42
42
|
return `${base.origin}${prefix}/c/${conversationId}`;
|
|
43
43
|
}
|
|
44
44
|
catch {
|
|
@@ -193,7 +193,7 @@ export async function openConversationFromSidebarWithRetry(Runtime, options, tim
|
|
|
193
193
|
}
|
|
194
194
|
export async function waitForPromptPreview(Runtime, promptPreview, timeoutMs) {
|
|
195
195
|
const needleFull = promptPreview.trim().toLowerCase().slice(0, 120);
|
|
196
|
-
const needleShort = needleFull.replace(/\\s*\\d{4,}\\s*$/,
|
|
196
|
+
const needleShort = needleFull.replace(/\\s*\\d{4,}\\s*$/, "").trim();
|
|
197
197
|
const needles = Array.from(new Set([needleFull, needleShort].filter(Boolean)));
|
|
198
198
|
if (needles.length === 0)
|
|
199
199
|
return false;
|
|
@@ -241,10 +241,10 @@ export async function waitForPromptPreview(Runtime, promptPreview, timeoutMs) {
|
|
|
241
241
|
}
|
|
242
242
|
export async function waitForLocationChange(Runtime, timeoutMs) {
|
|
243
243
|
const start = Date.now();
|
|
244
|
-
let lastHref =
|
|
244
|
+
let lastHref = "";
|
|
245
245
|
while (Date.now() - start < timeoutMs) {
|
|
246
|
-
const { result } = await Runtime.evaluate({ expression:
|
|
247
|
-
const href = typeof result?.value ===
|
|
246
|
+
const { result } = await Runtime.evaluate({ expression: "location.href", returnByValue: true });
|
|
247
|
+
const href = typeof result?.value === "string" ? result.value : "";
|
|
248
248
|
if (lastHref && href !== lastHref) {
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
@@ -259,9 +259,9 @@ export async function readConversationTurnIndex(Runtime, logger) {
|
|
|
259
259
|
expression: `document.querySelectorAll(${selectorLiteral}).length`,
|
|
260
260
|
returnByValue: true,
|
|
261
261
|
});
|
|
262
|
-
const raw = typeof result?.value ===
|
|
262
|
+
const raw = typeof result?.value === "number" ? result.value : Number(result?.value);
|
|
263
263
|
if (!Number.isFinite(raw)) {
|
|
264
|
-
throw new Error(
|
|
264
|
+
throw new Error("Turn count not numeric");
|
|
265
265
|
}
|
|
266
266
|
return Math.max(0, Math.floor(raw) - 1);
|
|
267
267
|
}
|
|
@@ -273,14 +273,19 @@ export async function readConversationTurnIndex(Runtime, logger) {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
function normalizeForComparison(text) {
|
|
276
|
-
return String(text ||
|
|
276
|
+
return String(text || "")
|
|
277
|
+
.toLowerCase()
|
|
278
|
+
.replace(/\\s+/g, " ")
|
|
279
|
+
.trim();
|
|
277
280
|
}
|
|
278
281
|
export function buildPromptEchoMatcher(promptPreview) {
|
|
279
|
-
const normalizedPrompt = normalizeForComparison(promptPreview ??
|
|
282
|
+
const normalizedPrompt = normalizeForComparison(promptPreview ?? "");
|
|
280
283
|
if (!normalizedPrompt) {
|
|
281
284
|
return null;
|
|
282
285
|
}
|
|
283
|
-
const promptPrefix = normalizedPrompt.length >= 80
|
|
286
|
+
const promptPrefix = normalizedPrompt.length >= 80
|
|
287
|
+
? normalizedPrompt.slice(0, Math.min(200, normalizedPrompt.length))
|
|
288
|
+
: "";
|
|
284
289
|
const minFragment = Math.min(40, normalizedPrompt.length);
|
|
285
290
|
return {
|
|
286
291
|
isEcho: (text) => {
|
|
@@ -294,11 +299,11 @@ export function buildPromptEchoMatcher(promptPreview) {
|
|
|
294
299
|
if (normalized.length >= minFragment && normalizedPrompt.startsWith(normalized)) {
|
|
295
300
|
return true;
|
|
296
301
|
}
|
|
297
|
-
if (normalized.includes(
|
|
298
|
-
const marker = normalized.includes(
|
|
302
|
+
if (normalized.includes("…") || normalized.includes("...")) {
|
|
303
|
+
const marker = normalized.includes("…") ? "…" : "...";
|
|
299
304
|
const [prefixRaw, suffixRaw] = normalized.split(marker);
|
|
300
|
-
const prefix = prefixRaw?.trim() ??
|
|
301
|
-
const suffix = suffixRaw?.trim() ??
|
|
305
|
+
const prefix = prefixRaw?.trim() ?? "";
|
|
306
|
+
const suffix = suffixRaw?.trim() ?? "";
|
|
302
307
|
if (!prefix && !suffix)
|
|
303
308
|
return false;
|
|
304
309
|
if (prefix && !normalizedPrompt.includes(prefix))
|
|
@@ -316,13 +321,13 @@ export async function recoverPromptEcho(Runtime, answer, matcher, logger, minTur
|
|
|
316
321
|
if (!matcher || !matcher.isEcho(answer.text)) {
|
|
317
322
|
return answer;
|
|
318
323
|
}
|
|
319
|
-
logger(
|
|
324
|
+
logger("Detected prompt echo while reattaching; waiting for assistant response...");
|
|
320
325
|
const deadline = Date.now() + Math.min(timeoutMs, 15_000);
|
|
321
326
|
let bestText = null;
|
|
322
327
|
let stableCount = 0;
|
|
323
328
|
while (Date.now() < deadline) {
|
|
324
329
|
const snapshot = await readAssistantSnapshot(Runtime, minTurnIndex ?? undefined).catch(() => null);
|
|
325
|
-
const text = typeof snapshot?.text ===
|
|
330
|
+
const text = typeof snapshot?.text === "string" ? snapshot.text.trim() : "";
|
|
326
331
|
if (!text || matcher.isEcho(text)) {
|
|
327
332
|
await delay(300);
|
|
328
333
|
continue;
|
|
@@ -340,7 +345,7 @@ export async function recoverPromptEcho(Runtime, answer, matcher, logger, minTur
|
|
|
340
345
|
await delay(300);
|
|
341
346
|
}
|
|
342
347
|
if (bestText) {
|
|
343
|
-
logger(
|
|
348
|
+
logger("Recovered assistant response after prompt echo during reattach");
|
|
344
349
|
return { ...answer, text: bestText };
|
|
345
350
|
}
|
|
346
351
|
return answer;
|
|
@@ -375,8 +380,8 @@ export function alignPromptEchoPair(answerText, answerMarkdown, matcher, logger,
|
|
|
375
380
|
}
|
|
376
381
|
export function alignPromptEchoMarkdown(answerText, answerMarkdown, matcher, logger) {
|
|
377
382
|
const aligned = alignPromptEchoPair(answerText, answerMarkdown, matcher, logger, {
|
|
378
|
-
text:
|
|
379
|
-
markdown:
|
|
383
|
+
text: "Aligned prompt-echo text to copied markdown during reattach",
|
|
384
|
+
markdown: "Aligned prompt-echo markdown to response text during reattach",
|
|
380
385
|
});
|
|
381
386
|
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
382
387
|
}
|