@steipete/oracle 0.19.0 → 0.20.1
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/bin/oracle-cli.js +72 -29
- package/dist/src/browser/actions/deepResearch.js +168 -44
- package/dist/src/browser/actions/modelSelection.js +78 -1
- package/dist/src/browser/actions/promptComposer.js +3 -0
- package/dist/src/browser/actions/thinkingTime.js +49 -6
- package/dist/src/browser/actions/webSearch.js +96 -0
- package/dist/src/browser/chromeLifecycle.js +28 -16
- package/dist/src/browser/config.js +1 -1
- package/dist/src/browser/index.js +144 -49
- package/dist/src/browser/liveTabs.js +11 -4
- package/dist/src/browser/profileState.js +6 -1
- package/dist/src/browser/promptFingerprint.js +54 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +1 -0
- package/dist/src/browser/reattach.js +178 -109
- package/dist/src/browser/recoveryTarget.js +156 -0
- package/dist/src/browser/sessionRunner.js +33 -8
- package/dist/src/browser/tabLeaseRegistry.js +20 -0
- package/dist/src/browser/targetClaim.js +54 -0
- package/dist/src/cli/browserConfig.js +33 -3
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/browserTabs.js +38 -3
- package/dist/src/cli/detach.js +10 -1
- package/dist/src/cli/detachedSession.js +36 -0
- package/dist/src/cli/options.js +15 -0
- package/dist/src/cli/recoveredBrowserHarvest.js +50 -0
- package/dist/src/cli/runOptions.js +19 -4
- package/dist/src/cli/sessionDisplay.js +15 -8
- package/dist/src/cli/sessionRunner.js +153 -41
- package/dist/src/mcp/tools/consult.js +2 -2
- package/dist/src/mcp/types.js +1 -1
- package/dist/src/oracle/config.js +14 -0
- package/dist/src/oracle/geminiModels.js +1 -0
- package/dist/src/oracle/run.js +27 -4
- package/dist/src/remote/client.js +3 -0
- package/dist/src/remote/server.js +2 -0
- package/dist/src/sessionManager.js +8 -1
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +10 -10
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { readSubmittedPromptFingerprint, readUserMessageIds } from "./promptFingerprint.js";
|
|
1
2
|
import { mkdtemp, rm, mkdir } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import net from "node:net";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { claimBrowserTarget } from "./targetClaim.js";
|
|
5
8
|
import { resolveBrowserConfig } from "./config.js";
|
|
6
9
|
import { copyChromeProfile } from "./profileCopy.js";
|
|
7
10
|
import { BrowserCancellation, withoutBrowserCancellation } from "./cancellation.js";
|
|
@@ -554,19 +557,13 @@ export function isLocalChromeHostForTest(host) {
|
|
|
554
557
|
return isLocalChromeHost(host);
|
|
555
558
|
}
|
|
556
559
|
async function closeRemoteConnectionAfterRun(options) {
|
|
557
|
-
if (options.connectionClosedUnexpectedly) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
560
|
if (!options.connection) {
|
|
561
561
|
await options.client?.close();
|
|
562
562
|
return;
|
|
563
563
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
await options.client?.close();
|
|
569
|
-
}
|
|
564
|
+
await options.connection.close({
|
|
565
|
+
preserveTarget: options.connectionClosedUnexpectedly || options.preserveTarget,
|
|
566
|
+
});
|
|
570
567
|
}
|
|
571
568
|
function shouldCloseOwnedRunTargetAfterRun(options) {
|
|
572
569
|
return (options.ownsTarget &&
|
|
@@ -712,8 +709,12 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
712
709
|
let lastTargetId;
|
|
713
710
|
let lastUrl;
|
|
714
711
|
let promptSubmitted = false;
|
|
712
|
+
let submittedPromptHash = null;
|
|
713
|
+
let ownedRecoveryTarget;
|
|
714
|
+
const targetClaimId = randomUUID();
|
|
715
715
|
let modelSelectionEvidence;
|
|
716
716
|
let thinkingSelectionEvidence;
|
|
717
|
+
let researchPlan;
|
|
717
718
|
let tabLease = null;
|
|
718
719
|
let conversationUrlMonitor = null;
|
|
719
720
|
const emitRuntimeHint = async () => {
|
|
@@ -729,8 +730,11 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
729
730
|
tabUrl: lastUrl,
|
|
730
731
|
conversationId,
|
|
731
732
|
promptSubmitted,
|
|
733
|
+
submittedPromptHash,
|
|
734
|
+
ownedRecoveryTarget,
|
|
732
735
|
userDataDir,
|
|
733
736
|
controllerPid: process.pid,
|
|
737
|
+
researchPlan,
|
|
734
738
|
};
|
|
735
739
|
try {
|
|
736
740
|
await runtimeHintCb?.(hint, modelSelectionEvidence);
|
|
@@ -747,10 +751,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
747
751
|
}
|
|
748
752
|
};
|
|
749
753
|
const markPromptSubmitted = async () => {
|
|
750
|
-
if (promptSubmitted) {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
754
|
promptSubmitted = true;
|
|
755
|
+
submittedPromptHash = null;
|
|
754
756
|
await emitRuntimeHint();
|
|
755
757
|
void conversationUrlMonitor?.schedule("post-submit", config.timeoutMs ?? 120_000);
|
|
756
758
|
};
|
|
@@ -929,7 +931,15 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
929
931
|
});
|
|
930
932
|
client = cancellation.client(connection.client);
|
|
931
933
|
isolatedTargetId = connection.targetId ?? null;
|
|
932
|
-
ownsTarget =
|
|
934
|
+
ownsTarget = Boolean(connection.targetId);
|
|
935
|
+
if (connection.targetId && (!config.keepBrowser || options.closeOwnedTabOnComplete)) {
|
|
936
|
+
ownedRecoveryTarget = {
|
|
937
|
+
host: chromeHost,
|
|
938
|
+
port: chrome.port,
|
|
939
|
+
targetId: connection.targetId,
|
|
940
|
+
claimId: targetClaimId,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
933
943
|
}
|
|
934
944
|
if (tabLease && isolatedTargetId) {
|
|
935
945
|
await tabLease.update({
|
|
@@ -977,7 +987,10 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
977
987
|
? extractConversationIdFromUrl(liveness.matchedUrl ?? lastUrl ?? "")
|
|
978
988
|
: undefined,
|
|
979
989
|
promptSubmitted,
|
|
990
|
+
submittedPromptHash,
|
|
991
|
+
ownedRecoveryTarget,
|
|
980
992
|
controllerPid: process.pid,
|
|
993
|
+
researchPlan,
|
|
981
994
|
},
|
|
982
995
|
}));
|
|
983
996
|
})();
|
|
@@ -990,6 +1003,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
990
1003
|
domainEnablers.push(DOM.enable());
|
|
991
1004
|
}
|
|
992
1005
|
await Promise.all(domainEnablers);
|
|
1006
|
+
if (config.browserTabRef)
|
|
1007
|
+
await claimBrowserTarget(Runtime, targetClaimId);
|
|
993
1008
|
if (!config.headless && config.hideWindow) {
|
|
994
1009
|
await positionChromeWindowOffscreen(client, userDataDir, logger);
|
|
995
1010
|
}
|
|
@@ -1251,6 +1266,7 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1251
1266
|
await withoutBrowserCancellation(() => handle.release()).catch(() => undefined);
|
|
1252
1267
|
};
|
|
1253
1268
|
const submitOnce = async (prompt, submissionAttachments) => {
|
|
1269
|
+
await claimBrowserTarget(Runtime, targetClaimId);
|
|
1254
1270
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
1255
1271
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
1256
1272
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
@@ -1313,10 +1329,12 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1313
1329
|
attachmentNames: attachmentExpectations,
|
|
1314
1330
|
attachmentNavigationUrl,
|
|
1315
1331
|
onPromptSubmitted: markPromptSubmitted,
|
|
1332
|
+
webSearch: config.researchMode === "search",
|
|
1316
1333
|
};
|
|
1317
1334
|
const deepResearchTargetBaseline = deepResearch && client
|
|
1318
1335
|
? await captureDeepResearchTargetBaseline(client, logger)
|
|
1319
1336
|
: undefined;
|
|
1337
|
+
const previousUserMessageIds = await readUserMessageIds(Runtime, config.inputTimeoutMs);
|
|
1320
1338
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
1321
1339
|
prompt,
|
|
1322
1340
|
evaluate: async () => undefined,
|
|
@@ -1326,6 +1344,11 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1326
1344
|
});
|
|
1327
1345
|
await markPromptSubmitted();
|
|
1328
1346
|
const providerBaselineTurns = providerState.baselineTurns;
|
|
1347
|
+
const renderedPromptHash = await readSubmittedPromptFingerprint(Runtime, previousUserMessageIds, config.inputTimeoutMs);
|
|
1348
|
+
if (renderedPromptHash) {
|
|
1349
|
+
submittedPromptHash = renderedPromptHash;
|
|
1350
|
+
await emitRuntimeHint();
|
|
1351
|
+
}
|
|
1329
1352
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
1330
1353
|
baselineTurns = providerBaselineTurns;
|
|
1331
1354
|
}
|
|
@@ -1387,7 +1410,17 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1387
1410
|
}
|
|
1388
1411
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
1389
1412
|
if (deepResearch) {
|
|
1390
|
-
await raceWithDisconnect(waitForResearchPlanAutoConfirm(Runtime, logger
|
|
1413
|
+
await raceWithDisconnect(waitForResearchPlanAutoConfirm(Runtime, logger, undefined, {
|
|
1414
|
+
Page,
|
|
1415
|
+
client,
|
|
1416
|
+
ignoredTargetKeys: deepResearchTargetKeys,
|
|
1417
|
+
targetBaselineCaptured: deepResearchTargetBaselineCaptured,
|
|
1418
|
+
minTurnIndex: baselineTurns,
|
|
1419
|
+
onPlan: async (plan) => {
|
|
1420
|
+
researchPlan = plan;
|
|
1421
|
+
await emitRuntimeHint();
|
|
1422
|
+
},
|
|
1423
|
+
}));
|
|
1391
1424
|
const researchResult = await raceWithDisconnect(waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, {
|
|
1392
1425
|
ignoredTargetKeys: deepResearchTargetKeys,
|
|
1393
1426
|
targetBaselineCaptured: deepResearchTargetBaselineCaptured,
|
|
@@ -1438,7 +1471,10 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1438
1471
|
tabUrl: lastUrl,
|
|
1439
1472
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1440
1473
|
promptSubmitted,
|
|
1474
|
+
submittedPromptHash,
|
|
1475
|
+
ownedRecoveryTarget,
|
|
1441
1476
|
controllerPid: process.pid,
|
|
1477
|
+
researchPlan,
|
|
1442
1478
|
};
|
|
1443
1479
|
}
|
|
1444
1480
|
// Helper to normalize text for echo detection (collapse whitespace, lowercase)
|
|
@@ -1528,6 +1564,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1528
1564
|
tabUrl: lastUrl,
|
|
1529
1565
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1530
1566
|
promptSubmitted,
|
|
1567
|
+
submittedPromptHash,
|
|
1568
|
+
ownedRecoveryTarget,
|
|
1531
1569
|
controllerPid: process.pid,
|
|
1532
1570
|
},
|
|
1533
1571
|
});
|
|
@@ -1584,6 +1622,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1584
1622
|
tabUrl: lastUrl,
|
|
1585
1623
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1586
1624
|
promptSubmitted,
|
|
1625
|
+
submittedPromptHash,
|
|
1626
|
+
ownedRecoveryTarget,
|
|
1587
1627
|
controllerPid: process.pid,
|
|
1588
1628
|
};
|
|
1589
1629
|
throw await createAssistantTimeoutError({
|
|
@@ -1809,6 +1849,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1809
1849
|
tabUrl: lastUrl,
|
|
1810
1850
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1811
1851
|
promptSubmitted,
|
|
1852
|
+
submittedPromptHash,
|
|
1853
|
+
ownedRecoveryTarget,
|
|
1812
1854
|
controllerPid: process.pid,
|
|
1813
1855
|
},
|
|
1814
1856
|
}),
|
|
@@ -1876,6 +1918,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1876
1918
|
tabUrl: lastUrl,
|
|
1877
1919
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1878
1920
|
promptSubmitted,
|
|
1921
|
+
submittedPromptHash,
|
|
1922
|
+
ownedRecoveryTarget,
|
|
1879
1923
|
controllerPid: process.pid,
|
|
1880
1924
|
};
|
|
1881
1925
|
}
|
|
@@ -1902,6 +1946,8 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1902
1946
|
chromeTargetId: lastTargetId,
|
|
1903
1947
|
tabUrl: lastUrl,
|
|
1904
1948
|
promptSubmitted,
|
|
1949
|
+
submittedPromptHash,
|
|
1950
|
+
ownedRecoveryTarget,
|
|
1905
1951
|
controllerPid: process.pid,
|
|
1906
1952
|
};
|
|
1907
1953
|
const reuseProfileHint = `oracle --engine browser --browser-manual-login ` +
|
|
@@ -1965,7 +2011,10 @@ async function runBrowserModeInternal(options, cancellation) {
|
|
|
1965
2011
|
? extractConversationIdFromUrl(liveness.matchedUrl ?? lastUrl ?? "")
|
|
1966
2012
|
: undefined,
|
|
1967
2013
|
promptSubmitted,
|
|
2014
|
+
submittedPromptHash,
|
|
2015
|
+
ownedRecoveryTarget,
|
|
1968
2016
|
controllerPid: process.pid,
|
|
2017
|
+
researchPlan,
|
|
1969
2018
|
},
|
|
1970
2019
|
}, normalizedError);
|
|
1971
2020
|
}
|
|
@@ -2363,8 +2412,12 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2363
2412
|
let tabLease = null;
|
|
2364
2413
|
let lastUrl;
|
|
2365
2414
|
let promptSubmitted = false;
|
|
2415
|
+
let submittedPromptHash = null;
|
|
2416
|
+
let ownedRecoveryTarget;
|
|
2417
|
+
const targetClaimId = randomUUID();
|
|
2366
2418
|
let modelSelectionEvidence;
|
|
2367
2419
|
let thinkingSelectionEvidence;
|
|
2420
|
+
let researchPlan;
|
|
2368
2421
|
let attachedExistingTab = false;
|
|
2369
2422
|
let ownsTarget = true;
|
|
2370
2423
|
let conversationUrlMonitor = null;
|
|
@@ -2382,7 +2435,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2382
2435
|
tabUrl: lastUrl,
|
|
2383
2436
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2384
2437
|
promptSubmitted,
|
|
2438
|
+
submittedPromptHash,
|
|
2439
|
+
ownedRecoveryTarget,
|
|
2385
2440
|
controllerPid: process.pid,
|
|
2441
|
+
researchPlan,
|
|
2386
2442
|
}, modelSelectionEvidence);
|
|
2387
2443
|
await tabLease?.update({
|
|
2388
2444
|
chromeHost: host,
|
|
@@ -2397,10 +2453,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2397
2453
|
}
|
|
2398
2454
|
};
|
|
2399
2455
|
const markPromptSubmitted = async () => {
|
|
2400
|
-
if (promptSubmitted) {
|
|
2401
|
-
return;
|
|
2402
|
-
}
|
|
2403
2456
|
promptSubmitted = true;
|
|
2457
|
+
submittedPromptHash = null;
|
|
2404
2458
|
await emitRuntimeHint();
|
|
2405
2459
|
void conversationUrlMonitor?.schedule("post-submit", config.timeoutMs ?? 120_000);
|
|
2406
2460
|
};
|
|
@@ -2452,7 +2506,16 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2452
2506
|
}), (connection) => connection.close());
|
|
2453
2507
|
client = cancellation.client(connection.client);
|
|
2454
2508
|
remoteTargetId = connection.targetId ?? null;
|
|
2455
|
-
ownsTarget =
|
|
2509
|
+
ownsTarget = Boolean(connection.targetId);
|
|
2510
|
+
if (connection.targetId && (!config.keepBrowser || options.closeOwnedTabOnComplete)) {
|
|
2511
|
+
ownedRecoveryTarget = {
|
|
2512
|
+
host,
|
|
2513
|
+
port,
|
|
2514
|
+
targetId: connection.targetId,
|
|
2515
|
+
browserWSEndpoint,
|
|
2516
|
+
claimId: targetClaimId,
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2456
2519
|
}
|
|
2457
2520
|
if (tabLease && remoteTargetId) {
|
|
2458
2521
|
await tabLease.update({
|
|
@@ -2472,6 +2535,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2472
2535
|
domainEnablers.push(DOM.enable());
|
|
2473
2536
|
}
|
|
2474
2537
|
await Promise.all(domainEnablers);
|
|
2538
|
+
if (config.browserTabRef)
|
|
2539
|
+
await claimBrowserTarget(Runtime, targetClaimId);
|
|
2475
2540
|
removeDialogHandler = installJavaScriptDialogAutoDismissal(Page, logger);
|
|
2476
2541
|
await enableFocusEmulation(client, logger, "remote target");
|
|
2477
2542
|
const activeConversationUrlMonitor = createConversationUrlMonitor({
|
|
@@ -2572,6 +2637,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2572
2637
|
});
|
|
2573
2638
|
}
|
|
2574
2639
|
const submitOnce = async (prompt, submissionAttachments) => {
|
|
2640
|
+
await claimBrowserTarget(Runtime, targetClaimId);
|
|
2575
2641
|
const baselineSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
2576
2642
|
const baselineAssistantText = typeof baselineSnapshot?.text === "string" ? baselineSnapshot.text.trim() : "";
|
|
2577
2643
|
const attachmentNames = submissionAttachments.map((a) => path.basename(a.path));
|
|
@@ -2629,10 +2695,12 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2629
2695
|
attachmentNames: attachmentExpectations,
|
|
2630
2696
|
attachmentNavigationUrl,
|
|
2631
2697
|
onPromptSubmitted: markPromptSubmitted,
|
|
2698
|
+
webSearch: config.researchMode === "search",
|
|
2632
2699
|
};
|
|
2633
2700
|
const deepResearchTargetBaseline = deepResearch && client
|
|
2634
2701
|
? await captureDeepResearchTargetBaseline(client, logger)
|
|
2635
2702
|
: undefined;
|
|
2703
|
+
const previousUserMessageIds = await readUserMessageIds(Runtime, config.inputTimeoutMs);
|
|
2636
2704
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
2637
2705
|
prompt,
|
|
2638
2706
|
evaluate: async () => undefined,
|
|
@@ -2642,6 +2710,11 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2642
2710
|
});
|
|
2643
2711
|
await markPromptSubmitted();
|
|
2644
2712
|
const providerBaselineTurns = providerState.baselineTurns;
|
|
2713
|
+
const renderedPromptHash = await readSubmittedPromptFingerprint(Runtime, previousUserMessageIds, config.inputTimeoutMs);
|
|
2714
|
+
if (renderedPromptHash) {
|
|
2715
|
+
submittedPromptHash = renderedPromptHash;
|
|
2716
|
+
await emitRuntimeHint();
|
|
2717
|
+
}
|
|
2645
2718
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
2646
2719
|
baselineTurns = providerBaselineTurns;
|
|
2647
2720
|
}
|
|
@@ -2679,7 +2752,17 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2679
2752
|
deepResearchTargetBaselineCaptured = submission.deepResearchTargetBaselineCaptured ?? false;
|
|
2680
2753
|
const imageArtifactMinTurnIndex = baselineTurns;
|
|
2681
2754
|
if (deepResearch) {
|
|
2682
|
-
await waitForResearchPlanAutoConfirm(Runtime, logger
|
|
2755
|
+
await waitForResearchPlanAutoConfirm(Runtime, logger, undefined, {
|
|
2756
|
+
Page,
|
|
2757
|
+
client,
|
|
2758
|
+
ignoredTargetKeys: deepResearchTargetKeys,
|
|
2759
|
+
targetBaselineCaptured: deepResearchTargetBaselineCaptured,
|
|
2760
|
+
minTurnIndex: baselineTurns,
|
|
2761
|
+
onPlan: async (plan) => {
|
|
2762
|
+
researchPlan = plan;
|
|
2763
|
+
await emitRuntimeHint();
|
|
2764
|
+
},
|
|
2765
|
+
});
|
|
2683
2766
|
const researchResult = await waitForDeepResearchCompletion(Runtime, logger, config.timeoutMs, baselineTurns, Page, client, {
|
|
2684
2767
|
ignoredTargetKeys: deepResearchTargetKeys,
|
|
2685
2768
|
targetBaselineCaptured: deepResearchTargetBaselineCaptured,
|
|
@@ -2728,7 +2811,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2728
2811
|
tabUrl: lastUrl,
|
|
2729
2812
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2730
2813
|
promptSubmitted,
|
|
2814
|
+
submittedPromptHash,
|
|
2815
|
+
ownedRecoveryTarget,
|
|
2731
2816
|
controllerPid: process.pid,
|
|
2817
|
+
researchPlan,
|
|
2732
2818
|
};
|
|
2733
2819
|
}
|
|
2734
2820
|
// Helper to normalize text for echo detection (collapse whitespace, lowercase)
|
|
@@ -2817,6 +2903,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2817
2903
|
tabUrl: lastUrl,
|
|
2818
2904
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2819
2905
|
promptSubmitted,
|
|
2906
|
+
submittedPromptHash,
|
|
2907
|
+
ownedRecoveryTarget,
|
|
2820
2908
|
controllerPid: process.pid,
|
|
2821
2909
|
},
|
|
2822
2910
|
});
|
|
@@ -2875,6 +2963,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2875
2963
|
tabUrl: lastUrl,
|
|
2876
2964
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2877
2965
|
promptSubmitted,
|
|
2966
|
+
submittedPromptHash,
|
|
2967
|
+
ownedRecoveryTarget,
|
|
2878
2968
|
controllerPid: process.pid,
|
|
2879
2969
|
};
|
|
2880
2970
|
throw await createAssistantTimeoutError({
|
|
@@ -3058,6 +3148,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3058
3148
|
tabUrl: lastUrl,
|
|
3059
3149
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
3060
3150
|
promptSubmitted,
|
|
3151
|
+
submittedPromptHash,
|
|
3152
|
+
ownedRecoveryTarget,
|
|
3061
3153
|
controllerPid: process.pid,
|
|
3062
3154
|
},
|
|
3063
3155
|
}),
|
|
@@ -3120,6 +3212,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3120
3212
|
tabUrl: lastUrl,
|
|
3121
3213
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
3122
3214
|
promptSubmitted,
|
|
3215
|
+
submittedPromptHash,
|
|
3216
|
+
ownedRecoveryTarget,
|
|
3123
3217
|
artifacts: savedArtifacts,
|
|
3124
3218
|
generatedImages: imageArtifacts.generatedImages,
|
|
3125
3219
|
savedImages: imageArtifacts.savedImages,
|
|
@@ -3168,7 +3262,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3168
3262
|
? extractConversationIdFromUrl(liveness.matchedUrl ?? lastUrl ?? "")
|
|
3169
3263
|
: undefined,
|
|
3170
3264
|
promptSubmitted,
|
|
3265
|
+
submittedPromptHash,
|
|
3266
|
+
ownedRecoveryTarget,
|
|
3171
3267
|
controllerPid: process.pid,
|
|
3268
|
+
researchPlan,
|
|
3172
3269
|
},
|
|
3173
3270
|
});
|
|
3174
3271
|
}
|
|
@@ -3176,17 +3273,6 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3176
3273
|
await withoutBrowserCancellation(async () => {
|
|
3177
3274
|
stopThinkingMonitor?.();
|
|
3178
3275
|
await conversationUrlMonitor?.stop();
|
|
3179
|
-
try {
|
|
3180
|
-
await closeRemoteConnectionAfterRun({
|
|
3181
|
-
connectionClosedUnexpectedly,
|
|
3182
|
-
connection,
|
|
3183
|
-
client,
|
|
3184
|
-
runStatus,
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
catch {
|
|
3188
|
-
// ignore
|
|
3189
|
-
}
|
|
3190
3276
|
removeDialogHandler?.();
|
|
3191
3277
|
const keepRemoteBrowser = Boolean(config.keepBrowser);
|
|
3192
3278
|
const shouldCloseOwnedRemoteTarget = shouldCloseOwnedRunTargetAfterRun({
|
|
@@ -3196,33 +3282,42 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
3196
3282
|
closeOwnedTabOnComplete: options.closeOwnedTabOnComplete,
|
|
3197
3283
|
closeOwnedTabOnCancel: options.closeOwnedTabOnCancel,
|
|
3198
3284
|
});
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
if (!replacementTargetId) {
|
|
3213
|
-
logger(`[browser] Remote Chrome page retention could not be verified after closing ${remoteTargetId}.`);
|
|
3285
|
+
const closeConnection = async () => {
|
|
3286
|
+
let preserveTarget = !shouldCloseOwnedRemoteTarget;
|
|
3287
|
+
if (!preserveTarget && keepRemoteBrowser && client && remoteTargetId) {
|
|
3288
|
+
try {
|
|
3289
|
+
const { targetInfos } = await client.Target.getTargets();
|
|
3290
|
+
if (!targetInfos.some((target) => target.type === "page" && target.targetId !== remoteTargetId)) {
|
|
3291
|
+
const replacement = await client.Target.createTarget({ url: "about:blank" });
|
|
3292
|
+
if (!replacement.targetId)
|
|
3293
|
+
preserveTarget = true;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
catch {
|
|
3297
|
+
preserveTarget = true;
|
|
3214
3298
|
}
|
|
3215
3299
|
}
|
|
3300
|
+
await closeRemoteConnectionAfterRun({
|
|
3301
|
+
connectionClosedUnexpectedly,
|
|
3302
|
+
connection,
|
|
3303
|
+
client,
|
|
3304
|
+
preserveTarget,
|
|
3305
|
+
});
|
|
3216
3306
|
};
|
|
3217
3307
|
if (tabLease) {
|
|
3218
3308
|
const handle = tabLease;
|
|
3219
3309
|
tabLease = null;
|
|
3220
|
-
await handle
|
|
3221
|
-
|
|
3222
|
-
|
|
3310
|
+
await handle.release({ onRelease: closeConnection }).catch(async () => {
|
|
3311
|
+
await closeRemoteConnectionAfterRun({
|
|
3312
|
+
connectionClosedUnexpectedly,
|
|
3313
|
+
connection,
|
|
3314
|
+
client,
|
|
3315
|
+
preserveTarget: true,
|
|
3316
|
+
}).catch(() => undefined);
|
|
3317
|
+
});
|
|
3223
3318
|
}
|
|
3224
3319
|
else {
|
|
3225
|
-
await
|
|
3320
|
+
await closeConnection().catch(() => undefined);
|
|
3226
3321
|
}
|
|
3227
3322
|
// Don't kill remote Chrome - it's not ours to manage
|
|
3228
3323
|
const totalSeconds = (Date.now() - startedAt) / 1000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import CDP from "chrome-remote-interface";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { ANSWER_SELECTORS, ASSISTANT_ROLE_SELECTOR, INPUT_SELECTORS, MODEL_BUTTON_SELECTOR, SEND_BUTTON_SELECTORS,
|
|
3
|
+
import { ANSWER_SELECTORS, ASSISTANT_ROLE_SELECTOR, INPUT_SELECTORS, MODEL_BUTTON_SELECTOR, SEND_BUTTON_SELECTORS, STOP_BUTTON_SELECTORS, } from "./constants.js";
|
|
4
4
|
import { captureAssistantMarkdown, readAssistantSnapshot } from "./actions/assistantResponse.js";
|
|
5
5
|
import { buildConversationTurnListExpression } from "./conversationTurns.js";
|
|
6
6
|
import { extractStableConversationIdFromUrl } from "./conversationUrl.js";
|
|
@@ -62,7 +62,7 @@ function buildTabInspectionExpression() {
|
|
|
62
62
|
const answerSelectorsLiteral = JSON.stringify(ANSWER_SELECTORS);
|
|
63
63
|
const assistantRoleLiteral = escapeLiteral(ASSISTANT_ROLE_SELECTOR);
|
|
64
64
|
const modelButtonSelectorLiteral = escapeLiteral(MODEL_BUTTON_SELECTOR);
|
|
65
|
-
const stopSelectorLiteral = escapeLiteral(
|
|
65
|
+
const stopSelectorLiteral = escapeLiteral(STOP_BUTTON_SELECTORS.join(","));
|
|
66
66
|
return `(() => {
|
|
67
67
|
const INPUT_SELECTORS = ${inputSelectorsLiteral};
|
|
68
68
|
const SEND_SELECTORS = ${sendSelectorsLiteral};
|
|
@@ -91,8 +91,7 @@ function buildTabInspectionExpression() {
|
|
|
91
91
|
const label = normalize(node.textContent || node.getAttribute('aria-label') || node.getAttribute('title'));
|
|
92
92
|
return LOGIN_CTA.test(label);
|
|
93
93
|
});
|
|
94
|
-
const
|
|
95
|
-
const stopExists = Boolean(stopButton && isVisible(stopButton));
|
|
94
|
+
const stopExists = Array.from(document.querySelectorAll(STOP_BUTTON_SELECTOR)).some(isVisible);
|
|
96
95
|
const sendButton = firstVisible(SEND_SELECTORS);
|
|
97
96
|
const sendExists = Boolean(sendButton);
|
|
98
97
|
const promptNode = firstVisible(INPUT_SELECTORS);
|
|
@@ -163,6 +162,8 @@ function buildTabInspectionExpression() {
|
|
|
163
162
|
const assistantCount = new Set(assistantOwners).size;
|
|
164
163
|
const lastAssistantText = normalize(lastAssistantNode?.textContent);
|
|
165
164
|
const lastUserText = normalize(lastUserTurn?.textContent);
|
|
165
|
+
const lastUserMessage = lastUserTurn?.matches?.('[data-message-author-role="user"]')
|
|
166
|
+
? lastUserTurn : lastUserTurn?.querySelector?.('[data-message-author-role="user"]');
|
|
166
167
|
const authenticated = !loginButtonExists && (promptReady || sendExists || stopExists || assistantCount > 0);
|
|
167
168
|
return {
|
|
168
169
|
title: normalize(document.title),
|
|
@@ -179,6 +180,8 @@ function buildTabInspectionExpression() {
|
|
|
179
180
|
lastAssistantTurnIndex,
|
|
180
181
|
lastUserTurnIndex,
|
|
181
182
|
lastUserText,
|
|
183
|
+
lastUserTextRaw: lastUserMessage?.textContent,
|
|
184
|
+
lastUserMessageId: lastUserMessage?.getAttribute?.('data-message-id'),
|
|
182
185
|
visibilityState: document.visibilityState,
|
|
183
186
|
focused: Boolean(document.hasFocus?.()),
|
|
184
187
|
};
|
|
@@ -264,6 +267,8 @@ export async function inspectChatGptTab(options) {
|
|
|
264
267
|
: undefined,
|
|
265
268
|
lastAssistantSnippet: trimToSnippet(lastAssistantText),
|
|
266
269
|
lastUserText,
|
|
270
|
+
lastUserTextRaw: info.lastUserTextRaw,
|
|
271
|
+
lastUserMessageId: info.lastUserMessageId,
|
|
267
272
|
lastUserSnippet: trimToSnippet(lastUserText),
|
|
268
273
|
focused: Boolean(info.focused),
|
|
269
274
|
visibilityState: typeof info.visibilityState === "string" ? info.visibilityState : "",
|
|
@@ -462,6 +467,8 @@ export async function harvestChatGptTab(options = {}) {
|
|
|
462
467
|
harvested.authenticated = followup.authenticated;
|
|
463
468
|
harvested.loginButtonExists = followup.loginButtonExists;
|
|
464
469
|
harvested.lastUserText = followup.lastUserText;
|
|
470
|
+
harvested.lastUserTextRaw = followup.lastUserTextRaw;
|
|
471
|
+
harvested.lastUserMessageId = followup.lastUserMessageId;
|
|
465
472
|
harvested.lastUserSnippet = followup.lastUserSnippet;
|
|
466
473
|
harvested.assistantFollowsLatestUser = followup.assistantFollowsLatestUser;
|
|
467
474
|
harvested.lastAssistantTurnIndex = followup.lastAssistantTurnIndex;
|
|
@@ -224,7 +224,12 @@ export async function readProcessStartTimeMs(pid) {
|
|
|
224
224
|
if (Math.trunc(pid) === process.pid) {
|
|
225
225
|
// Use the same OS identity as peer controllers, not wall time minus uptime.
|
|
226
226
|
// Cache our own PID only: it cannot be reused during this process's lifetime.
|
|
227
|
-
return (ownProcessStartTime ??= queryProcessStartTimeMs(process.pid))
|
|
227
|
+
return (ownProcessStartTime ??= queryProcessStartTimeMs(process.pid).then((startedAt) => {
|
|
228
|
+
// A transient probe failure is not a permanent process identity.
|
|
229
|
+
if (startedAt === null)
|
|
230
|
+
ownProcessStartTime = undefined;
|
|
231
|
+
return startedAt;
|
|
232
|
+
}));
|
|
228
233
|
}
|
|
229
234
|
return queryProcessStartTimeMs(pid);
|
|
230
235
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { buildConversationTurnListExpression } from "./conversationTurns.js";
|
|
3
|
+
export function browserPromptFingerprint(value, messageId) {
|
|
4
|
+
return createHash("sha256")
|
|
5
|
+
.update(JSON.stringify([messageId, String(value ?? "").replace(/\r\n?/g, "\n")]))
|
|
6
|
+
.digest("hex");
|
|
7
|
+
}
|
|
8
|
+
export function readUserMessageIds(runtime, timeoutMs = 0) {
|
|
9
|
+
return readDomUntil(runtime, `Array.from(document.querySelectorAll('[data-message-author-role="user"]'), user => user.getAttribute('data-message-id'))`, timeoutMs, (value) => Array.isArray(value) && value.every((id) => typeof id === "string" && id.trim())
|
|
10
|
+
? value
|
|
11
|
+
: undefined);
|
|
12
|
+
}
|
|
13
|
+
export async function readSubmittedPromptFingerprint(runtime, previousMessageIds, timeoutMs = 0) {
|
|
14
|
+
if (previousMessageIds === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const previous = new Set(previousMessageIds);
|
|
17
|
+
return readDomUntil(runtime, `(() => {
|
|
18
|
+
const turns = ${buildConversationTurnListExpression()};
|
|
19
|
+
for (let index = turns.length - 1; index >= 0; index--) {
|
|
20
|
+
const turn = turns[index];
|
|
21
|
+
const user = turn.matches('[data-message-author-role="user"]') ? turn : turn.querySelector('[data-message-author-role="user"]');
|
|
22
|
+
if (user) return { text: user.textContent, messageId: user.getAttribute('data-message-id') };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
})()`, timeoutMs, (value) => {
|
|
26
|
+
const turn = value;
|
|
27
|
+
if (typeof turn?.text === "string" &&
|
|
28
|
+
turn.text.trim() &&
|
|
29
|
+
typeof turn.messageId === "string" &&
|
|
30
|
+
turn.messageId.trim() &&
|
|
31
|
+
!previous.has(turn.messageId))
|
|
32
|
+
return browserPromptFingerprint(turn.text, turn.messageId);
|
|
33
|
+
return undefined;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function readDomUntil(runtime, expression, timeoutMs, select) {
|
|
37
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
38
|
+
for (;;) {
|
|
39
|
+
try {
|
|
40
|
+
const result = await runtime.evaluate({ expression, returnByValue: true });
|
|
41
|
+
const selected = select(result.result?.value);
|
|
42
|
+
if (selected !== undefined)
|
|
43
|
+
return selected;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
if (!/Cannot find (?:default )?(?:execution )?context|Execution context (?:was destroyed|is not available)/i.test(message))
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (Date.now() >= deadline)
|
|
51
|
+
return undefined;
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(100, deadline - Date.now())));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -27,6 +27,7 @@ async function submitPromptViaAdapter(ctx) {
|
|
|
27
27
|
inputTimeoutMs: state.inputTimeoutMs ?? undefined,
|
|
28
28
|
attachmentTimeoutMs: state.attachmentTimeoutMs ?? undefined,
|
|
29
29
|
onPromptSubmitted: state.onPromptSubmitted,
|
|
30
|
+
webSearch: state.webSearch,
|
|
30
31
|
}, ctx.prompt, state.logger);
|
|
31
32
|
state.committedTurns =
|
|
32
33
|
typeof committedTurns === "number" && Number.isFinite(committedTurns) ? committedTurns : null;
|