@steipete/oracle 0.15.2 → 0.16.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/README.md +1 -1
- package/dist/bin/oracle-cli.js +1 -1
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/assistantResponse.js +218 -126
- package/dist/src/browser/actions/modelSelection.js +133 -22
- package/dist/src/browser/actions/navigation.js +235 -14
- package/dist/src/browser/actions/thinkingStatus.js +228 -0
- package/dist/src/browser/actions/thinkingTime.js +68 -11
- package/dist/src/browser/cdpLiveness.js +78 -0
- package/dist/src/browser/chromeLifecycle.js +112 -30
- package/dist/src/browser/config.js +14 -1
- package/dist/src/browser/constants.js +5 -1
- package/dist/src/browser/controlPlan.js +2 -2
- package/dist/src/browser/conversationUrl.js +16 -0
- package/dist/src/browser/conversationUrlMonitor.js +2 -4
- package/dist/src/browser/cookies.js +6 -4
- package/dist/src/browser/index.js +213 -57
- package/dist/src/browser/liveTabs.js +115 -33
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/projectSourcesRunner.js +4 -4
- package/dist/src/browser/reattach.js +2 -2
- package/dist/src/browser/reattachHelpers.js +3 -5
- package/dist/src/browser/reattachability.js +2 -1
- package/dist/src/browser/recoverConversation.js +90 -29
- package/dist/src/browser/tabLeaseRegistry.js +9 -3
- package/dist/src/cli/browserConfig.js +5 -1
- package/dist/src/cli/browserDefaults.js +4 -1
- package/dist/src/cli/browserTabs.js +54 -33
- package/dist/src/cli/options.js +24 -0
- package/dist/src/cli/runOptions.js +6 -1
- package/dist/src/cli/sessionDisplay.js +38 -14
- package/dist/src/cli/sessionRunner.js +49 -2
- package/dist/src/oracle/config.js +25 -0
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/remote/server.js +8 -0
- 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 +13 -14
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -416
- package/dist/docs-site/browser-mode.html +0 -594
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -462
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -375
- package/dist/docs-site/gemini.html +0 -383
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -596
- package/dist/docs-site/mcp.html +0 -391
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -399
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -2,18 +2,15 @@ import { rm } from "node:fs/promises";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import net from "node:net";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
5
|
import CDP from "chrome-remote-interface";
|
|
8
6
|
import { launch, Launcher } from "chrome-launcher";
|
|
9
7
|
import { cleanupStaleProfileState } from "./profileState.js";
|
|
10
8
|
import { delay } from "./utils.js";
|
|
11
|
-
const execFileAsync = promisify(execFile);
|
|
12
9
|
export async function launchChrome(config, userDataDir, logger) {
|
|
13
10
|
const connectHost = resolveRemoteDebugHost();
|
|
14
11
|
const debugBindAddress = connectHost && connectHost !== "127.0.0.1" ? "0.0.0.0" : connectHost;
|
|
15
12
|
const debugPort = config.debugPort ?? parseDebugPortEnv();
|
|
16
|
-
const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress);
|
|
13
|
+
const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress, config.hideWindow ?? false);
|
|
17
14
|
const usePatchedLauncher = Boolean(connectHost && connectHost !== "127.0.0.1");
|
|
18
15
|
// copy-profile reuses a copied signed-in profile whose cookies are
|
|
19
16
|
// Keychain-encrypted, so it must launch with the real Keychain (not mocked):
|
|
@@ -46,6 +43,24 @@ export async function launchChrome(config, userDataDir, logger) {
|
|
|
46
43
|
logger(`Launched Chrome${pidLabel} on port ${launcher.port}${hostLabel}`);
|
|
47
44
|
return Object.assign(launcher, { host: connectHost ?? "127.0.0.1" });
|
|
48
45
|
}
|
|
46
|
+
export async function positionChromeWindowOffscreen(client, logger) {
|
|
47
|
+
if (process.platform !== "darwin") {
|
|
48
|
+
logger("Window hiding is only supported on macOS");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const { windowId } = await client.Browser.getWindowForTarget();
|
|
53
|
+
await client.Browser.setWindowBounds({
|
|
54
|
+
windowId,
|
|
55
|
+
bounds: { left: -32_000, top: -32_000, windowState: "normal" },
|
|
56
|
+
});
|
|
57
|
+
logger("Chrome window positioned off-screen");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
logger(`Failed to position Chrome window off-screen: ${message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
49
64
|
export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logger, opts) {
|
|
50
65
|
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
51
66
|
let handling;
|
|
@@ -110,29 +125,6 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
110
125
|
}
|
|
111
126
|
};
|
|
112
127
|
}
|
|
113
|
-
export async function hideChromeWindow(chrome, logger) {
|
|
114
|
-
if (process.platform !== "darwin") {
|
|
115
|
-
logger("Window hiding is only supported on macOS");
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (!chrome.pid) {
|
|
119
|
-
logger("Unable to hide window: missing Chrome PID");
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const script = `tell application "System Events"
|
|
123
|
-
try
|
|
124
|
-
set visible of (first process whose unix id is ${chrome.pid}) to false
|
|
125
|
-
end try
|
|
126
|
-
end tell`;
|
|
127
|
-
try {
|
|
128
|
-
await execFileAsync("osascript", ["-e", script]);
|
|
129
|
-
logger("Chrome window hidden (Cmd-H)");
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
-
logger(`Failed to hide Chrome window: ${message}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
128
|
export async function connectToChrome(port, logger, host) {
|
|
137
129
|
const client = await CDP({ port, host });
|
|
138
130
|
logger("Connected to Chrome DevTools protocol");
|
|
@@ -414,13 +406,79 @@ export async function closeTab(port, targetId, logger, host) {
|
|
|
414
406
|
const effectiveHost = host ?? "127.0.0.1";
|
|
415
407
|
try {
|
|
416
408
|
await CDP.Close({ host: effectiveHost, port, id: targetId });
|
|
417
|
-
|
|
409
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
410
|
+
await delay(25);
|
|
411
|
+
let targets;
|
|
412
|
+
try {
|
|
413
|
+
targets = (await CDP.List({ host: effectiveHost, port }));
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (!targets.some((target) => (target.targetId ?? target.id) === targetId)) {
|
|
419
|
+
logger(`Closed isolated browser tab (target=${targetId})`);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
logger(`Browser tab close was not confirmed (target=${targetId})`);
|
|
424
|
+
return false;
|
|
418
425
|
}
|
|
419
426
|
catch (error) {
|
|
427
|
+
try {
|
|
428
|
+
const targets = (await CDP.List({ host: effectiveHost, port }));
|
|
429
|
+
if (!targets.some((target) => (target.targetId ?? target.id) === targetId)) {
|
|
430
|
+
logger(`Closed isolated browser tab (target=${targetId})`);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// Preserve the original close error below.
|
|
436
|
+
}
|
|
420
437
|
const message = error instanceof Error ? error.message : String(error);
|
|
421
438
|
logger(`Failed to close browser tab ${targetId}: ${message}`);
|
|
439
|
+
return false;
|
|
422
440
|
}
|
|
423
441
|
}
|
|
442
|
+
export async function createChromePageTarget(port, logger, host) {
|
|
443
|
+
const effectiveHost = host ?? "127.0.0.1";
|
|
444
|
+
try {
|
|
445
|
+
const created = (await CDP.New({
|
|
446
|
+
host: effectiveHost,
|
|
447
|
+
port,
|
|
448
|
+
url: "about:blank",
|
|
449
|
+
}));
|
|
450
|
+
const createdTargetId = created.targetId ?? created.id;
|
|
451
|
+
if (!createdTargetId) {
|
|
452
|
+
logger("Failed to create a replacement Chrome tab.");
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
logger(`Opened replacement Chrome tab (target=${createdTargetId})`);
|
|
456
|
+
return createdTargetId;
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
460
|
+
logger(`Failed to create a replacement Chrome tab: ${message}`);
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
export async function ensureChromePageTargetAfterClose(port, closingTargetId, logger, host) {
|
|
465
|
+
const effectiveHost = host ?? "127.0.0.1";
|
|
466
|
+
try {
|
|
467
|
+
const targets = (await CDP.List({ host: effectiveHost, port }));
|
|
468
|
+
const existingPageTargetId = targets
|
|
469
|
+
.filter((target) => target.type === "page")
|
|
470
|
+
.map((target) => target.targetId ?? target.id)
|
|
471
|
+
.find((targetId) => Boolean(targetId) && targetId !== closingTargetId);
|
|
472
|
+
if (existingPageTargetId) {
|
|
473
|
+
return existingPageTargetId;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
478
|
+
logger(`Failed to inspect Chrome tabs before closing ${closingTargetId}: ${message}`);
|
|
479
|
+
}
|
|
480
|
+
return await createChromePageTarget(port, logger, host);
|
|
481
|
+
}
|
|
424
482
|
export async function closeBlankChromeTabs(port, logger, host, options) {
|
|
425
483
|
const effectiveHost = host ?? "127.0.0.1";
|
|
426
484
|
const excluded = new Set([...(options?.excludeTargetIds ?? [])].filter((targetId) => typeof targetId === "string" && targetId.length > 0));
|
|
@@ -433,10 +491,20 @@ export async function closeBlankChromeTabs(port, logger, host, options) {
|
|
|
433
491
|
logger(`Failed to inspect blank Chrome tabs: ${message}`);
|
|
434
492
|
return;
|
|
435
493
|
}
|
|
494
|
+
const preservedBlankTargetId = options?.preserveOneBlank
|
|
495
|
+
? targets
|
|
496
|
+
.filter(isBlankPageTarget)
|
|
497
|
+
.map((target) => target.targetId ?? target.id)
|
|
498
|
+
.filter((targetId) => Boolean(targetId))
|
|
499
|
+
.sort()[0]
|
|
500
|
+
: undefined;
|
|
436
501
|
let closed = 0;
|
|
437
502
|
for (const target of targets) {
|
|
438
503
|
const targetId = target.targetId ?? target.id;
|
|
439
|
-
if (!targetId ||
|
|
504
|
+
if (!targetId ||
|
|
505
|
+
targetId === preservedBlankTargetId ||
|
|
506
|
+
excluded.has(targetId) ||
|
|
507
|
+
!isBlankPageTarget(target)) {
|
|
440
508
|
continue;
|
|
441
509
|
}
|
|
442
510
|
try {
|
|
@@ -459,7 +527,7 @@ function isBlankPageTarget(target) {
|
|
|
459
527
|
const url = (target.url ?? "").trim().toLowerCase();
|
|
460
528
|
return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
|
|
461
529
|
}
|
|
462
|
-
function buildChromeFlags(headless, debugBindAddress) {
|
|
530
|
+
function buildChromeFlags(headless, debugBindAddress, hideWindow = false) {
|
|
463
531
|
const flags = [
|
|
464
532
|
"--disable-background-networking",
|
|
465
533
|
"--disable-background-timer-throttling",
|
|
@@ -489,8 +557,22 @@ function buildChromeFlags(headless, debugBindAddress) {
|
|
|
489
557
|
if (headless) {
|
|
490
558
|
flags.push("--headless=new");
|
|
491
559
|
}
|
|
560
|
+
else if (hideWindow && process.platform === "darwin") {
|
|
561
|
+
// Cmd-H stops macOS Chrome from compositing the page, which can swallow
|
|
562
|
+
// trusted CDP clicks and retain the prompt as a draft. Keeping the window
|
|
563
|
+
// off-screen avoids desktop disruption while preserving normal rendering.
|
|
564
|
+
flags.push("--window-position=-32000,-32000");
|
|
565
|
+
}
|
|
566
|
+
// Opt-in only: container/CI Chromium often cannot use the sandbox. Callers must
|
|
567
|
+
// set ORACLE_CHROME_NO_SANDBOX=1 explicitly (never default this on).
|
|
568
|
+
if (process.env.ORACLE_CHROME_NO_SANDBOX === "1") {
|
|
569
|
+
flags.push("--no-sandbox", "--disable-dev-shm-usage");
|
|
570
|
+
}
|
|
492
571
|
return flags;
|
|
493
572
|
}
|
|
573
|
+
export function buildChromeFlagsForTest(headless, debugBindAddress, hideWindow = false) {
|
|
574
|
+
return buildChromeFlags(headless, debugBindAddress, hideWindow);
|
|
575
|
+
}
|
|
494
576
|
function resolveChromeLaunchOptions(chromeFlags, usingCopiedProfile) {
|
|
495
577
|
if (!usingCopiedProfile) {
|
|
496
578
|
return { chromeFlags, ignoreDefaultFlags: false };
|
|
@@ -61,6 +61,7 @@ export function resolveBrowserConfig(config) {
|
|
|
61
61
|
const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
|
|
62
62
|
const envAllowCookieErrors = (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim().toLowerCase() === "true" ||
|
|
63
63
|
(process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim() === "1";
|
|
64
|
+
const envMaxConcurrentTabs = parseMaxConcurrentTabs(process.env.ORACLE_BROWSER_MAX_CONCURRENT_TABS);
|
|
64
65
|
const rawUrl = config?.chatgptUrl ?? config?.url ?? DEFAULT_BROWSER_CONFIG.url;
|
|
65
66
|
const normalizedUrl = normalizeChatgptUrl(rawUrl ?? DEFAULT_BROWSER_CONFIG.url, DEFAULT_BROWSER_CONFIG.url);
|
|
66
67
|
const desiredModel = config?.desiredModel ?? DEFAULT_BROWSER_CONFIG.desiredModel ?? DEFAULT_MODEL_TARGET;
|
|
@@ -87,7 +88,7 @@ export function resolveBrowserConfig(config) {
|
|
|
87
88
|
assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
|
|
88
89
|
reuseChromeWaitMs: config?.reuseChromeWaitMs ?? DEFAULT_BROWSER_CONFIG.reuseChromeWaitMs,
|
|
89
90
|
profileLockTimeoutMs: config?.profileLockTimeoutMs ?? DEFAULT_BROWSER_CONFIG.profileLockTimeoutMs,
|
|
90
|
-
maxConcurrentTabs: normalizeMaxConcurrentTabs(config?.maxConcurrentTabs ?? DEFAULT_BROWSER_CONFIG.maxConcurrentTabs),
|
|
91
|
+
maxConcurrentTabs: normalizeMaxConcurrentTabs(config?.maxConcurrentTabs ?? envMaxConcurrentTabs ?? DEFAULT_BROWSER_CONFIG.maxConcurrentTabs),
|
|
91
92
|
autoReattachDelayMs: config?.autoReattachDelayMs ?? DEFAULT_BROWSER_CONFIG.autoReattachDelayMs,
|
|
92
93
|
autoReattachIntervalMs: config?.autoReattachIntervalMs ?? DEFAULT_BROWSER_CONFIG.autoReattachIntervalMs,
|
|
93
94
|
autoReattachTimeoutMs: config?.autoReattachTimeoutMs ?? DEFAULT_BROWSER_CONFIG.autoReattachTimeoutMs,
|
|
@@ -134,6 +135,18 @@ function parseDebugPort(raw) {
|
|
|
134
135
|
}
|
|
135
136
|
return value;
|
|
136
137
|
}
|
|
138
|
+
function parseMaxConcurrentTabs(raw) {
|
|
139
|
+
if (!raw)
|
|
140
|
+
return null;
|
|
141
|
+
const trimmed = raw.trim();
|
|
142
|
+
if (!/^\d+$/.test(trimmed))
|
|
143
|
+
return null;
|
|
144
|
+
const value = Number(trimmed);
|
|
145
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
137
150
|
function resolveManualLoginProfileDir(...candidates) {
|
|
138
151
|
for (const candidate of candidates) {
|
|
139
152
|
const profileDir = candidate?.trim();
|
|
@@ -64,10 +64,14 @@ export const UPLOAD_STATUS_SELECTORS = [
|
|
|
64
64
|
'[aria-live="assertive"]',
|
|
65
65
|
];
|
|
66
66
|
export const STOP_BUTTON_SELECTOR = '[data-testid="stop-button"]';
|
|
67
|
+
// The aria-label fallback exists for data-testid drift, but a document-wide match makes ANY
|
|
68
|
+
// visible "stop" control (read-aloud, voice/dictation) read as "still generating", which blocks
|
|
69
|
+
// completion until the response timeout. Scope it to the composer form and exclude the known
|
|
70
|
+
// non-generation stop controls that legitimately live there.
|
|
67
71
|
export const STOP_BUTTON_SELECTORS = [
|
|
68
72
|
STOP_BUTTON_SELECTOR,
|
|
69
73
|
'[data-testid="composer-stop-button"]',
|
|
70
|
-
'button[aria-label*="stop" i]',
|
|
74
|
+
'form button[aria-label*="stop" i]:not([aria-label*="dictat" i]):not([aria-label*="voice" i]):not([aria-label*="read" i])',
|
|
71
75
|
];
|
|
72
76
|
export const SEND_BUTTON_SELECTORS = [
|
|
73
77
|
'button[data-testid="send-button"]',
|
|
@@ -45,13 +45,13 @@ export function describeBrowserControlPlan(config = {}) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
if (config.hideWindow) {
|
|
48
|
-
guidance.push("
|
|
48
|
+
guidance.push("On macOS, Oracle launches Chrome off-screen while keeping the page rendered.");
|
|
49
49
|
guidance.push("For the calmest shared-desktop flow, prefer --browser-attach-running or --remote-chrome.");
|
|
50
50
|
return {
|
|
51
51
|
mode: "hidden-window",
|
|
52
52
|
launchesChrome: true,
|
|
53
53
|
mayFocusWindow: true,
|
|
54
|
-
summary: "launch Chrome
|
|
54
|
+
summary: "launch Chrome in hidden-window mode",
|
|
55
55
|
guidance,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const CONVERSATION_ID_PATH = /\/c\/([a-zA-Z0-9-]+)(?=[/?#]|$)/;
|
|
2
|
+
/**
|
|
3
|
+
* Extract a durable ChatGPT conversation id from a URL.
|
|
4
|
+
*
|
|
5
|
+
* ChatGPT can briefly expose client-created routes such as `/c/WEB:<request-id>`
|
|
6
|
+
* before replacing them with the persisted conversation URL. Those transient
|
|
7
|
+
* routes must not be used to scope assistant-response capture or reattachment.
|
|
8
|
+
*/
|
|
9
|
+
export function extractStableConversationIdFromUrl(url) {
|
|
10
|
+
if (!url)
|
|
11
|
+
return undefined;
|
|
12
|
+
return url.match(CONVERSATION_ID_PATH)?.[1];
|
|
13
|
+
}
|
|
14
|
+
export function isStableConversationUrl(url) {
|
|
15
|
+
return extractStableConversationIdFromUrl(url) !== undefined;
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { delay } from "./utils.js";
|
|
2
|
+
import { isStableConversationUrl } from "./conversationUrl.js";
|
|
2
3
|
export function createConversationUrlMonitor(options) {
|
|
3
4
|
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
4
5
|
const wait = options.wait ?? delay;
|
|
@@ -14,7 +15,7 @@ export function createConversationUrlMonitor(options) {
|
|
|
14
15
|
if (stopped) {
|
|
15
16
|
return false;
|
|
16
17
|
}
|
|
17
|
-
if (url &&
|
|
18
|
+
if (url && isStableConversationUrl(url)) {
|
|
18
19
|
options.logger(`[browser] conversation url (${label}) = ${url}`);
|
|
19
20
|
const persist = options.persistUrl(url);
|
|
20
21
|
activePersists.add(persist);
|
|
@@ -59,6 +60,3 @@ export function createConversationUrlMonitor(options) {
|
|
|
59
60
|
},
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
function isConversationUrl(url) {
|
|
63
|
-
return /\/c\/[a-z0-9-]+/i.test(url);
|
|
64
|
-
}
|
|
@@ -244,12 +244,14 @@ function normalizeExpiration(expires) {
|
|
|
244
244
|
if (value <= 0) {
|
|
245
245
|
return undefined;
|
|
246
246
|
}
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
// Units by magnitude (do not treat Unix seconds ~1.7e9 as milliseconds):
|
|
248
|
+
// - >= 1e15: Chrome/WebKit FILETIME microseconds since 1601
|
|
249
|
+
// - >= 1e12: Unix milliseconds
|
|
250
|
+
// - else: Unix seconds (sweet-cookie / Chromium Cookie.expires)
|
|
251
|
+
if (value >= 1_000_000_000_000_000) {
|
|
249
252
|
return Math.round(value / 1_000_000 - 11644473600);
|
|
250
253
|
}
|
|
251
|
-
if (value
|
|
252
|
-
// Likely milliseconds; normalize to seconds for CDP.
|
|
254
|
+
if (value >= 1_000_000_000_000) {
|
|
253
255
|
return Math.round(value / 1000);
|
|
254
256
|
}
|
|
255
257
|
return Math.round(value);
|