@steipete/oracle 0.18.0 → 0.20.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/README.md +1 -1
- package/dist/bin/oracle-cli.js +76 -76
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/archiveConversation.js +31 -7
- package/dist/src/browser/actions/assistantResponse.js +43 -1
- package/dist/src/browser/actions/attachmentContext.js +239 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +19 -1
- package/dist/src/browser/actions/attachmentEvidence.js +78 -0
- package/dist/src/browser/actions/attachmentProgress.js +41 -0
- package/dist/src/browser/actions/attachmentPrompt.js +130 -0
- package/dist/src/browser/actions/attachments.js +306 -355
- package/dist/src/browser/actions/deepResearch.js +170 -45
- package/dist/src/browser/actions/modelSelection.js +91 -4
- package/dist/src/browser/actions/navigation.js +1 -1
- package/dist/src/browser/actions/promptComposer.js +364 -186
- package/dist/src/browser/actions/remoteFileTransfer.js +4 -2
- package/dist/src/browser/actions/thinkingStatus.js +2 -5
- package/dist/src/browser/actions/thinkingTime.js +156 -16
- package/dist/src/browser/actions/webSearch.js +96 -0
- package/dist/src/browser/attachRunning.js +46 -2
- package/dist/src/browser/attachmentValidation.js +53 -0
- package/dist/src/browser/cancellation.js +105 -0
- package/dist/src/browser/chatgptImages.js +2 -0
- package/dist/src/browser/chatgptThrottle.js +104 -0
- package/dist/src/browser/chromeLifecycle.js +272 -47
- package/dist/src/browser/config.js +14 -1
- package/dist/src/browser/detect.js +1 -1
- package/dist/src/browser/index.js +525 -274
- package/dist/src/browser/liveTabs.js +3 -4
- package/dist/src/browser/modelDisplay.js +9 -0
- package/dist/src/browser/pageActions.js +2 -2
- package/dist/src/browser/profileState.js +114 -9
- package/dist/src/browser/projectSourcesRunner.js +83 -15
- package/dist/src/browser/prompt.js +272 -113
- package/dist/src/browser/providers/chatgptDomProvider.js +3 -0
- package/dist/src/browser/reattach.js +31 -4
- package/dist/src/browser/recoveryTarget.js +131 -0
- package/dist/src/browser/sessionRunner.js +73 -11
- package/dist/src/browser/tabLeaseRegistry.js +434 -38
- package/dist/src/browser/targetClaim.js +54 -0
- package/dist/src/browser/utils.js +20 -2
- package/dist/src/cli/browserConfig.js +36 -4
- package/dist/src/cli/browserDefaults.js +10 -0
- package/dist/src/cli/browserTabs.js +15 -29
- package/dist/src/cli/detachedSession.js +69 -0
- package/dist/src/cli/dryRun.js +58 -48
- package/dist/src/cli/harvestIntegrity.js +140 -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/serveBrowserConfig.js +24 -0
- package/dist/src/cli/sessionDisplay.js +13 -7
- package/dist/src/cli/sessionRunner.js +95 -12
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/client.js +67 -21
- package/dist/src/gemini-web/executor.js +4 -0
- package/dist/src/gemini-web/http.js +15 -0
- package/dist/src/gemini-web/models.js +15 -11
- package/dist/src/mcp/server.js +11 -14
- package/dist/src/mcp/tools/chatgptImage.js +4 -4
- package/dist/src/mcp/tools/consult.js +86 -24
- package/dist/src/mcp/tools/projectSources.js +4 -6
- package/dist/src/mcp/tools/sessionResources.js +1 -1
- package/dist/src/mcp/tools/sessions.js +2 -2
- package/dist/src/mcp/tools/wait.js +182 -0
- package/dist/src/mcp/types.js +8 -1
- package/dist/src/oracle/config.js +14 -0
- package/dist/src/oracle/errors.js +11 -0
- package/dist/src/oracle/geminiModels.js +1 -0
- package/dist/src/oracle/providerRoutePlan.js +3 -2
- package/dist/src/oracle/run.js +27 -4
- package/dist/src/remote/client.js +73 -6
- package/dist/src/remote/health.js +6 -2
- package/dist/src/remote/runSlots.js +83 -0
- package/dist/src/remote/server.js +346 -71
- package/dist/src/sessionManager.js +36 -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 +25 -23
- 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 -683
- 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 -400
- package/dist/docs-site/browser-mode.html +0 -588
- 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 -436
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -368
- package/dist/docs-site/gemini.html +0 -379
- 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 -595
- package/dist/docs-site/mcp.html +0 -368
- 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 -387
- 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/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/types.js +0 -1
- 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/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/profileSync.js +0 -141
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -6,6 +6,7 @@ import { isKnownModel } from "../oracle/modelResolver.js";
|
|
|
6
6
|
import { buildPromptMarkdown } from "../oracle/promptAssembly.js";
|
|
7
7
|
import { buildAttachmentPlan } from "./policies.js";
|
|
8
8
|
import { createStoredZip } from "./zipBundle.js";
|
|
9
|
+
import { buildAttachmentBasenameCollisionDetails, findAttachmentBasenameCollisions, formatAttachmentBasenameCollisionMessage, } from "./attachmentValidation.js";
|
|
9
10
|
const DEFAULT_BROWSER_INLINE_CHAR_BUDGET = 60_000;
|
|
10
11
|
const MAX_BROWSER_ATTACHMENTS = 10;
|
|
11
12
|
const MAX_BROWSER_ZIP_BUNDLE_BYTES = 128 * 1024 * 1024;
|
|
@@ -82,70 +83,197 @@ export function isRawUploadFile(filePath) {
|
|
|
82
83
|
const ext = path.extname(filePath).toLowerCase();
|
|
83
84
|
return MEDIA_EXTENSIONS.has(ext) || ARCHIVE_EXTENSIONS.has(ext);
|
|
84
85
|
}
|
|
86
|
+
const GENERATED_BUNDLE_DIR_PREFIX = "oracle-browser-bundle-";
|
|
87
|
+
const pendingFallbackBundles = new WeakMap();
|
|
85
88
|
function formatSectionsForBundle(sections, options = {}) {
|
|
86
89
|
return formatFileSections(sections, {
|
|
87
90
|
lineNumbers: options.lineNumbers ?? true,
|
|
88
91
|
trailingNewline: true,
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
|
-
function resolveBrowserBundleFormat(format,
|
|
94
|
+
function resolveBrowserBundleFormat(format, { hasRawUploadFiles }) {
|
|
92
95
|
if (format !== "auto") {
|
|
93
96
|
return format;
|
|
94
97
|
}
|
|
95
|
-
return
|
|
98
|
+
return hasRawUploadFiles ? "zip" : "text";
|
|
96
99
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
function resolveBrowserBundleScope(format, { bundleRequested, rawAttachmentCount, textAttachmentCount, }) {
|
|
101
|
+
const attachmentCount = textAttachmentCount + rawAttachmentCount;
|
|
102
|
+
if (attachmentCount === 0) {
|
|
103
|
+
return "none";
|
|
104
|
+
}
|
|
105
|
+
if (format === "text") {
|
|
106
|
+
if (textAttachmentCount === 0) {
|
|
107
|
+
return "none";
|
|
108
|
+
}
|
|
109
|
+
const shouldBundleText = bundleRequested || textAttachmentCount > 1 || attachmentCount > MAX_BROWSER_ATTACHMENTS;
|
|
110
|
+
return shouldBundleText ? "text-only" : "none";
|
|
111
|
+
}
|
|
112
|
+
if (bundleRequested) {
|
|
113
|
+
return "all";
|
|
102
114
|
}
|
|
103
|
-
|
|
115
|
+
// Preserve native uploads for images, PDFs, archives, and other raw inputs.
|
|
116
|
+
// Multiple text/source files benefit from a real filesystem tree, so bundle
|
|
117
|
+
// those into one ZIP while leaving any native attachments alongside it.
|
|
118
|
+
if (textAttachmentCount > 1) {
|
|
119
|
+
return rawAttachmentCount + 1 <= MAX_BROWSER_ATTACHMENTS ? "text-only" : "all";
|
|
120
|
+
}
|
|
121
|
+
return attachmentCount > MAX_BROWSER_ATTACHMENTS ? "all" : "none";
|
|
122
|
+
}
|
|
123
|
+
function appendZipBundleInstruction(composerText, originalCount, bundlePath) {
|
|
124
|
+
const fileLabel = originalCount === 1 ? "file" : "files";
|
|
125
|
+
const instruction = [
|
|
126
|
+
`The attached \`${path.basename(bundlePath)}\` contains ${originalCount} selected ${fileLabel} with relative paths preserved.`,
|
|
127
|
+
"Extract it into a temporary directory, then inspect the resulting file tree with filesystem and search tools before answering.",
|
|
128
|
+
].join(" ");
|
|
129
|
+
return [composerText, instruction].filter(Boolean).join("\n\n").trim();
|
|
130
|
+
}
|
|
131
|
+
function assertAttachmentCount(attachmentCount, format) {
|
|
132
|
+
if (attachmentCount <= MAX_BROWSER_ATTACHMENTS)
|
|
133
|
+
return;
|
|
134
|
+
throw new Error(`Browser upload has ${attachmentCount} attachments after applying bundle format "${format}". Use --browser-bundle-format auto or zip to stay within the ${MAX_BROWSER_ATTACHMENTS}-attachment limit.`);
|
|
104
135
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
136
|
+
function assertUniqueAttachmentBasenames(attachments, cwd) {
|
|
137
|
+
const collisions = findAttachmentBasenameCollisions(attachments);
|
|
138
|
+
if (collisions.length === 0)
|
|
107
139
|
return;
|
|
108
|
-
|
|
140
|
+
const details = buildAttachmentBasenameCollisionDetails(collisions, (attachment) => path.isAbsolute(attachment.path) ? attachment.path : path.resolve(cwd, attachment.path));
|
|
141
|
+
throw new FileValidationError(formatAttachmentBasenameCollisionMessage("Browser upload", details.collisions), { ...details });
|
|
142
|
+
}
|
|
143
|
+
async function applyWrittenBundle({ sections, sources, format, scope, rawUploadAttachments, composerText, bundleParentDir, }) {
|
|
144
|
+
const nativeAttachments = scope === "text-only" ? rawUploadAttachments : [];
|
|
145
|
+
assertAttachmentCount(1 + nativeAttachments.length, format);
|
|
146
|
+
assertUniqueAttachmentBasenames(nativeAttachments, process.cwd());
|
|
147
|
+
const reservedNames = new Set(nativeAttachments.map((a) => path.basename(a.path).toLowerCase()));
|
|
148
|
+
let bundleName = `attachments-bundle.${format === "zip" ? "zip" : "txt"}`;
|
|
149
|
+
for (let suffix = 2; reservedNames.has(bundleName.toLowerCase()); suffix += 1) {
|
|
150
|
+
bundleName = `attachments-bundle-${suffix}.${format === "zip" ? "zip" : "txt"}`;
|
|
151
|
+
}
|
|
152
|
+
const writtenBundle = await writeBrowserBundle(sections, sources, format, bundleName, bundleParentDir);
|
|
153
|
+
const attachments = [writtenBundle.attachment];
|
|
154
|
+
if (scope === "text-only") {
|
|
155
|
+
attachments.push(...rawUploadAttachments);
|
|
156
|
+
}
|
|
157
|
+
assertAttachmentCount(attachments.length, format);
|
|
158
|
+
return {
|
|
159
|
+
attachments,
|
|
160
|
+
bundled: writtenBundle.metadata,
|
|
161
|
+
composerText: format === "zip"
|
|
162
|
+
? appendZipBundleInstruction(composerText, writtenBundle.metadata.originalCount, writtenBundle.metadata.bundlePath)
|
|
163
|
+
: composerText,
|
|
164
|
+
tokenEstimateText: writtenBundle.tokenEstimateText,
|
|
165
|
+
};
|
|
109
166
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
167
|
+
function generatedBundleDirectory(attachment) {
|
|
168
|
+
if (!attachment.generatedBundle)
|
|
169
|
+
return null;
|
|
170
|
+
const dir = path.dirname(attachment.path);
|
|
171
|
+
return path.basename(dir).startsWith(GENERATED_BUNDLE_DIR_PREFIX) ? dir : null;
|
|
172
|
+
}
|
|
173
|
+
export function listGeneratedBrowserBundleDirs(artifacts) {
|
|
174
|
+
const dirs = new Set();
|
|
175
|
+
for (const attachment of [...artifacts.attachments, ...(artifacts.fallback?.attachments ?? [])]) {
|
|
176
|
+
const dir = generatedBundleDirectory(attachment);
|
|
177
|
+
if (dir)
|
|
178
|
+
dirs.add(dir);
|
|
179
|
+
}
|
|
180
|
+
return [...dirs];
|
|
181
|
+
}
|
|
182
|
+
export async function cleanupGeneratedBrowserBundles(artifacts) {
|
|
183
|
+
await Promise.all(listGeneratedBrowserBundleDirs(artifacts).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
|
184
|
+
}
|
|
185
|
+
export async function materializeBrowserFallback(artifacts) {
|
|
186
|
+
const pending = pendingFallbackBundles.get(artifacts);
|
|
187
|
+
if (!pending || !artifacts.fallback) {
|
|
188
|
+
return artifacts.fallback ?? null;
|
|
189
|
+
}
|
|
190
|
+
pendingFallbackBundles.delete(artifacts);
|
|
191
|
+
const applied = await applyWrittenBundle({
|
|
192
|
+
sections: pending.sections,
|
|
193
|
+
sources: pending.scope === "all" ? pending.allSources : pending.textSources,
|
|
194
|
+
format: pending.format,
|
|
195
|
+
scope: pending.scope,
|
|
196
|
+
rawUploadAttachments: pending.rawUploadAttachments,
|
|
197
|
+
composerText: artifacts.fallback.composerText,
|
|
114
198
|
});
|
|
199
|
+
artifacts.fallback.composerText = applied.composerText;
|
|
200
|
+
artifacts.fallback.attachments = applied.attachments;
|
|
201
|
+
artifacts.fallback.bundled = applied.bundled;
|
|
202
|
+
artifacts.fallback.pendingBundle = null;
|
|
203
|
+
return artifacts.fallback;
|
|
204
|
+
}
|
|
205
|
+
export async function materializeStagedFallbackBundle({ composerText, attachments, format, scope, bundleParentDir, }) {
|
|
206
|
+
const textAttachments = attachments.filter((attachment) => !isRawUploadFile(attachment.path));
|
|
207
|
+
const rawAttachments = attachments.filter((attachment) => isRawUploadFile(attachment.path));
|
|
208
|
+
const textSources = textAttachments.map((attachment) => ({
|
|
209
|
+
absolutePath: attachment.path,
|
|
210
|
+
displayPath: attachment.displayPath,
|
|
211
|
+
sizeBytes: attachment.sizeBytes ?? 0,
|
|
212
|
+
}));
|
|
213
|
+
const allSources = [
|
|
214
|
+
...textSources,
|
|
215
|
+
...rawAttachments.map((attachment) => ({
|
|
216
|
+
absolutePath: attachment.path,
|
|
217
|
+
displayPath: attachment.displayPath,
|
|
218
|
+
sizeBytes: attachment.sizeBytes ?? 0,
|
|
219
|
+
})),
|
|
220
|
+
];
|
|
221
|
+
const sections = await Promise.all(textAttachments.map(async (attachment, index) => {
|
|
222
|
+
const content = await fs.readFile(attachment.path, "utf8");
|
|
223
|
+
return {
|
|
224
|
+
index: index + 1,
|
|
225
|
+
absolutePath: attachment.path,
|
|
226
|
+
displayPath: attachment.displayPath,
|
|
227
|
+
content,
|
|
228
|
+
sectionText: "",
|
|
229
|
+
};
|
|
230
|
+
}));
|
|
231
|
+
return applyWrittenBundle({
|
|
232
|
+
sections,
|
|
233
|
+
sources: scope === "all" ? allSources : textSources,
|
|
234
|
+
format,
|
|
235
|
+
scope,
|
|
236
|
+
rawUploadAttachments: rawAttachments,
|
|
237
|
+
composerText,
|
|
238
|
+
bundleParentDir,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function writeBrowserBundle(sections, sources, format, bundleName, bundleParentDir = os.tmpdir()) {
|
|
242
|
+
const tokenEstimateText = formatSectionsForBundle(sections, { lineNumbers: format === "text" });
|
|
243
|
+
let content = tokenEstimateText;
|
|
115
244
|
if (format === "zip") {
|
|
116
245
|
const totalSourceBytes = sources.reduce((total, source) => total + source.sizeBytes, 0);
|
|
117
246
|
if (totalSourceBytes > MAX_BROWSER_ZIP_BUNDLE_BYTES) {
|
|
118
247
|
throw new Error(`Browser ZIP bundle inputs exceed the ${MAX_BROWSER_ZIP_BUNDLE_BYTES}-byte in-memory limit.`);
|
|
119
248
|
}
|
|
120
|
-
|
|
121
|
-
const buffer = createStoredZip(await Promise.all(sources.map(async (source) => ({
|
|
249
|
+
content = createStoredZip(await Promise.all(sources.map(async (source) => ({
|
|
122
250
|
path: source.displayPath,
|
|
123
251
|
content: await fs.readFile(source.absolutePath),
|
|
124
252
|
}))));
|
|
125
|
-
|
|
253
|
+
}
|
|
254
|
+
const bundleDir = await fs.mkdtemp(path.join(bundleParentDir, GENERATED_BUNDLE_DIR_PREFIX));
|
|
255
|
+
const bundlePath = path.join(bundleDir, bundleName);
|
|
256
|
+
try {
|
|
257
|
+
await fs.writeFile(bundlePath, content);
|
|
126
258
|
return {
|
|
127
259
|
attachment: {
|
|
128
260
|
path: bundlePath,
|
|
129
261
|
displayPath: bundlePath,
|
|
130
|
-
sizeBytes:
|
|
262
|
+
sizeBytes: Buffer.byteLength(content),
|
|
131
263
|
generatedBundle: true,
|
|
132
264
|
},
|
|
133
|
-
metadata: {
|
|
265
|
+
metadata: {
|
|
266
|
+
originalCount: format === "zip" ? sources.length : sections.length,
|
|
267
|
+
bundlePath,
|
|
268
|
+
format,
|
|
269
|
+
},
|
|
134
270
|
tokenEstimateText,
|
|
135
271
|
};
|
|
136
272
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
path: bundlePath,
|
|
142
|
-
displayPath: bundlePath,
|
|
143
|
-
sizeBytes: Buffer.byteLength(tokenEstimateText, "utf8"),
|
|
144
|
-
generatedBundle: true,
|
|
145
|
-
},
|
|
146
|
-
metadata: { originalCount: sections.length, bundlePath, format },
|
|
147
|
-
tokenEstimateText,
|
|
148
|
-
};
|
|
273
|
+
catch (error) {
|
|
274
|
+
await fs.rm(bundleDir, { recursive: true, force: true });
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
149
277
|
}
|
|
150
278
|
export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
151
279
|
const cwd = deps.cwd ?? process.cwd();
|
|
@@ -206,10 +334,10 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
206
334
|
.filter(Boolean)
|
|
207
335
|
.join("\n\n")
|
|
208
336
|
.trim();
|
|
209
|
-
const selectedPlan = attachmentsPolicy === "
|
|
210
|
-
?
|
|
211
|
-
: attachmentsPolicy === "
|
|
212
|
-
?
|
|
337
|
+
const selectedPlan = attachmentsPolicy === "never"
|
|
338
|
+
? inlinePlan
|
|
339
|
+
: attachmentsPolicy === "always" || bundleRequested
|
|
340
|
+
? uploadPlan
|
|
213
341
|
: inlineComposerText.length <= DEFAULT_BROWSER_INLINE_CHAR_BUDGET || sections.length === 0
|
|
214
342
|
? inlinePlan
|
|
215
343
|
: uploadPlan;
|
|
@@ -228,13 +356,13 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
228
356
|
const resolvedBundleFormat = resolveBrowserBundleFormat(bundleFormat, {
|
|
229
357
|
hasRawUploadFiles: rawUploadAttachments.length > 0,
|
|
230
358
|
});
|
|
231
|
-
const
|
|
232
|
-
attachmentCount: attachments.length,
|
|
359
|
+
const bundleScope = resolveBrowserBundleScope(resolvedBundleFormat, {
|
|
233
360
|
bundleRequested,
|
|
234
|
-
|
|
235
|
-
|
|
361
|
+
rawAttachmentCount: rawUploadAttachments.length,
|
|
362
|
+
textAttachmentCount: selectedPlan.attachments.length,
|
|
236
363
|
});
|
|
237
|
-
const
|
|
364
|
+
const shouldBundle = bundleScope !== "none";
|
|
365
|
+
let composerText = (!shouldBundle && selectedPlan.inlineBlock
|
|
238
366
|
? [...baseComposerSections, selectedPlan.inlineBlock]
|
|
239
367
|
: baseComposerSections)
|
|
240
368
|
.filter(Boolean)
|
|
@@ -242,85 +370,116 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
242
370
|
.trim();
|
|
243
371
|
let bundleText = null;
|
|
244
372
|
let bundled = null;
|
|
245
|
-
if (
|
|
246
|
-
const writtenBundle = await
|
|
373
|
+
if (bundleScope !== "none") {
|
|
374
|
+
const writtenBundle = await applyWrittenBundle({
|
|
375
|
+
sections,
|
|
376
|
+
sources: bundleScope === "all" ? allBundleSources : textBundleSources,
|
|
377
|
+
format: resolvedBundleFormat,
|
|
378
|
+
scope: bundleScope,
|
|
379
|
+
rawUploadAttachments,
|
|
380
|
+
composerText,
|
|
381
|
+
});
|
|
247
382
|
bundleText = writtenBundle.tokenEstimateText;
|
|
248
383
|
attachments.length = 0;
|
|
249
|
-
attachments.push(writtenBundle.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
bundled = writtenBundle.metadata;
|
|
384
|
+
attachments.push(...writtenBundle.attachments);
|
|
385
|
+
bundled = writtenBundle.bundled;
|
|
386
|
+
composerText = writtenBundle.composerText;
|
|
254
387
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
388
|
+
else {
|
|
389
|
+
assertAttachmentCount(attachments.length, resolvedBundleFormat);
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
assertUniqueAttachmentBasenames(attachments, cwd);
|
|
393
|
+
const inlineFileCount = shouldBundle ? 0 : selectedPlan.inlineFileCount;
|
|
394
|
+
const modelConfig = isKnownModel(runOptions.model)
|
|
395
|
+
? MODEL_CONFIGS[runOptions.model]
|
|
396
|
+
: MODEL_CONFIGS["gpt-5.1"];
|
|
397
|
+
const tokenizer = deps.tokenizeImpl ?? modelConfig.tokenizer;
|
|
398
|
+
const tokenizerUserSections = [userPrompt];
|
|
399
|
+
if (inlineFileCount > 0 && selectedPlan.inlineBlock) {
|
|
400
|
+
tokenizerUserSections.push(selectedPlan.inlineBlock);
|
|
401
|
+
}
|
|
402
|
+
if (shouldBundle && resolvedBundleFormat === "zip" && bundled) {
|
|
403
|
+
tokenizerUserSections.push(appendZipBundleInstruction("", bundled.originalCount, bundled.bundlePath));
|
|
404
|
+
}
|
|
405
|
+
const tokenizerUserContent = tokenizerUserSections
|
|
263
406
|
.filter((value) => Boolean(value?.trim()))
|
|
264
407
|
.join("\n\n")
|
|
265
|
-
.trim()
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
408
|
+
.trim();
|
|
409
|
+
const tokenizerMessages = [
|
|
410
|
+
systemPrompt ? { role: "system", content: systemPrompt } : null,
|
|
411
|
+
tokenizerUserContent ? { role: "user", content: tokenizerUserContent } : null,
|
|
412
|
+
].filter(Boolean);
|
|
413
|
+
let estimatedInputTokens = tokenizer(tokenizerMessages.length > 0 ? tokenizerMessages : [{ role: "user", content: "" }], TOKENIZER_OPTIONS);
|
|
414
|
+
const tokenEstimateIncludesInlineFiles = inlineFileCount > 0 && Boolean(selectedPlan.inlineBlock);
|
|
415
|
+
if (!tokenEstimateIncludesInlineFiles && sections.length > 0) {
|
|
416
|
+
const attachmentText = bundleText ?? formatFileSections(sections, { lineNumbers: false });
|
|
417
|
+
const attachmentTokens = tokenizer([{ role: "user", content: attachmentText }], TOKENIZER_OPTIONS);
|
|
418
|
+
estimatedInputTokens += attachmentTokens;
|
|
419
|
+
}
|
|
420
|
+
let fallback = null;
|
|
421
|
+
let pendingFallback;
|
|
422
|
+
if (attachmentsPolicy === "auto" && selectedPlan.mode === "inline" && sections.length > 0) {
|
|
423
|
+
const fallbackComposerText = baseComposerSections.join("\n\n").trim();
|
|
424
|
+
const fallbackAttachments = [...uploadPlan.attachments, ...rawUploadAttachments];
|
|
425
|
+
const fallbackBundleFormat = resolveBrowserBundleFormat(bundleFormat, {
|
|
426
|
+
hasRawUploadFiles: rawUploadAttachments.length > 0,
|
|
427
|
+
});
|
|
428
|
+
const fallbackBundleScope = resolveBrowserBundleScope(fallbackBundleFormat, {
|
|
429
|
+
bundleRequested,
|
|
430
|
+
rawAttachmentCount: rawUploadAttachments.length,
|
|
431
|
+
textAttachmentCount: uploadPlan.attachments.length,
|
|
432
|
+
});
|
|
433
|
+
fallback = {
|
|
434
|
+
composerText: fallbackComposerText,
|
|
435
|
+
attachments: fallbackAttachments,
|
|
436
|
+
bundled: null,
|
|
437
|
+
pendingBundle: fallbackBundleScope === "none"
|
|
438
|
+
? null
|
|
439
|
+
: { format: fallbackBundleFormat, scope: fallbackBundleScope },
|
|
440
|
+
};
|
|
441
|
+
if (fallbackBundleScope !== "none") {
|
|
442
|
+
pendingFallback = {
|
|
443
|
+
format: fallbackBundleFormat,
|
|
444
|
+
scope: fallbackBundleScope,
|
|
445
|
+
sections,
|
|
446
|
+
textSources: textBundleSources,
|
|
447
|
+
allSources: allBundleSources,
|
|
448
|
+
rawUploadAttachments,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
assertAttachmentCount(fallbackAttachments.length, fallbackBundleFormat);
|
|
298
453
|
}
|
|
299
|
-
fallbackBundled = writtenBundle.metadata;
|
|
300
454
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
composerText
|
|
304
|
-
|
|
305
|
-
|
|
455
|
+
const artifacts = {
|
|
456
|
+
markdown,
|
|
457
|
+
composerText,
|
|
458
|
+
estimatedInputTokens,
|
|
459
|
+
attachments,
|
|
460
|
+
inlineFileCount,
|
|
461
|
+
tokenEstimateIncludesInlineFiles,
|
|
462
|
+
attachmentsPolicy,
|
|
463
|
+
attachmentMode: shouldBundle
|
|
464
|
+
? "bundle"
|
|
465
|
+
: attachments.length > 0
|
|
466
|
+
? "upload"
|
|
467
|
+
: selectedPlan.mode === "bundle"
|
|
468
|
+
? "inline"
|
|
469
|
+
: selectedPlan.mode,
|
|
470
|
+
fallback,
|
|
471
|
+
bundled,
|
|
306
472
|
};
|
|
473
|
+
if (pendingFallback) {
|
|
474
|
+
pendingFallbackBundles.set(artifacts, pendingFallback);
|
|
475
|
+
}
|
|
476
|
+
return artifacts;
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
await Promise.all(attachments
|
|
480
|
+
.map(generatedBundleDirectory)
|
|
481
|
+
.filter((dir) => dir !== null)
|
|
482
|
+
.map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
|
483
|
+
throw error;
|
|
307
484
|
}
|
|
308
|
-
return {
|
|
309
|
-
markdown,
|
|
310
|
-
composerText,
|
|
311
|
-
estimatedInputTokens,
|
|
312
|
-
attachments,
|
|
313
|
-
inlineFileCount,
|
|
314
|
-
tokenEstimateIncludesInlineFiles,
|
|
315
|
-
attachmentsPolicy,
|
|
316
|
-
attachmentMode: shouldBundle
|
|
317
|
-
? "bundle"
|
|
318
|
-
: attachments.length > 0
|
|
319
|
-
? "upload"
|
|
320
|
-
: selectedPlan.mode === "bundle"
|
|
321
|
-
? "inline"
|
|
322
|
-
: selectedPlan.mode,
|
|
323
|
-
fallback,
|
|
324
|
-
bundled,
|
|
325
|
-
};
|
|
326
485
|
}
|
|
@@ -20,11 +20,14 @@ async function submitPromptViaAdapter(ctx) {
|
|
|
20
20
|
const committedTurns = await submitPrompt({
|
|
21
21
|
runtime: state.runtime,
|
|
22
22
|
input: state.input,
|
|
23
|
+
page: state.page,
|
|
23
24
|
attachmentNames: state.attachmentNames ?? [],
|
|
25
|
+
attachmentNavigationUrl: state.attachmentNavigationUrl,
|
|
24
26
|
baselineTurns: state.baselineTurns ?? undefined,
|
|
25
27
|
inputTimeoutMs: state.inputTimeoutMs ?? undefined,
|
|
26
28
|
attachmentTimeoutMs: state.attachmentTimeoutMs ?? undefined,
|
|
27
29
|
onPromptSubmitted: state.onPromptSubmitted,
|
|
30
|
+
webSearch: state.webSearch,
|
|
28
31
|
}, ctx.prompt, state.logger);
|
|
29
32
|
state.committedTurns =
|
|
30
33
|
typeof committedTurns === "number" && Number.isFinite(committedTurns) ? committedTurns : null;
|
|
@@ -3,8 +3,8 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
|
5
5
|
import { waitForAssistantResponse, captureAssistantMarkdown, navigateToChatGPT, ensureNotBlocked, ensureLoggedIn, ensurePromptReady, waitForResumedConversationHydration, } from "./pageActions.js";
|
|
6
|
-
import { launchChrome, connectToChrome, positionChromeWindowOffscreen, connectToRemoteChromeTarget, listRemoteChromeTargets, } from "./chromeLifecycle.js";
|
|
7
|
-
import { resolveBrowserConfig } from "./config.js";
|
|
6
|
+
import { launchChrome, connectToChrome, positionChromeWindowOffscreen, positionChromeWindowOnscreen, connectToRemoteChromeTarget, listRemoteChromeTargets, } from "./chromeLifecycle.js";
|
|
7
|
+
import { resolveBrowserApprovalWait, resolveBrowserConfig } from "./config.js";
|
|
8
8
|
import { clearStaleChatGptConversationCookies, syncCookies } from "./cookies.js";
|
|
9
9
|
import { CHATGPT_URL } from "./constants.js";
|
|
10
10
|
import { buildConversationTurnListExpression } from "./conversationTurns.js";
|
|
@@ -31,11 +31,14 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
31
31
|
const host = liveRuntime.chromeHost ?? "127.0.0.1";
|
|
32
32
|
const port = liveRuntime.chromePort ?? inferPortFromBrowserWSEndpoint(liveRuntime.chromeBrowserWSEndpoint);
|
|
33
33
|
const browserWSEndpoint = liveRuntime.chromeBrowserWSEndpoint ?? undefined;
|
|
34
|
+
const approvalWaitMs = resolveBrowserApprovalWait(config?.approvalWaitMs);
|
|
34
35
|
const listTargets = deps.listTargets ??
|
|
35
36
|
(async () => (await listRemoteChromeTargets({
|
|
36
37
|
host,
|
|
37
38
|
port: port ?? 9222,
|
|
38
39
|
browserWSEndpoint,
|
|
40
|
+
approvalWaitMs,
|
|
41
|
+
logger,
|
|
39
42
|
})));
|
|
40
43
|
const targetList = (await listTargets());
|
|
41
44
|
const target = pickTarget(targetList, liveRuntime);
|
|
@@ -44,6 +47,7 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
44
47
|
browserWSEndpoint,
|
|
45
48
|
targetId: target?.targetId ?? target?.id,
|
|
46
49
|
closeTargetOnDispose: false,
|
|
50
|
+
approvalWaitMs,
|
|
47
51
|
})
|
|
48
52
|
: await (async () => {
|
|
49
53
|
const client = (await (deps.connect ?? ((options) => CDP(options)))(browserWSEndpoint
|
|
@@ -62,6 +66,19 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
62
66
|
closeAttachedConnection = () => connection.close();
|
|
63
67
|
const client = connection.client;
|
|
64
68
|
const { Runtime, DOM, Page } = client;
|
|
69
|
+
const captureIdentity = async () => {
|
|
70
|
+
const targetId = target?.targetId ?? target?.id;
|
|
71
|
+
if (!targetId || !port)
|
|
72
|
+
return undefined;
|
|
73
|
+
const { result } = await withTimeout(Runtime.evaluate({ expression: "location.href", returnByValue: true }), 2_000, "Recovery target identity unavailable");
|
|
74
|
+
return {
|
|
75
|
+
host,
|
|
76
|
+
port,
|
|
77
|
+
targetId,
|
|
78
|
+
browserWSEndpoint,
|
|
79
|
+
conversationId: extractConversationIdFromUrl(typeof result?.value === "string" ? result.value : ""),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
65
82
|
if (Runtime?.enable) {
|
|
66
83
|
await Runtime.enable();
|
|
67
84
|
}
|
|
@@ -113,10 +130,12 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
113
130
|
const researchResult = await withTimeout(waitForDeepResearch(Runtime, logger, timeoutMs, minTurnIndex ?? undefined, Page, client, {
|
|
114
131
|
requireScopedTargetOwner: true,
|
|
115
132
|
}), timeoutMs + 5_000, "Reattach Deep Research response timed out");
|
|
133
|
+
const captureTarget = await captureIdentity().catch(() => undefined);
|
|
116
134
|
await closeAttached();
|
|
117
135
|
return {
|
|
118
136
|
answerText: researchResult.text,
|
|
119
137
|
answerMarkdown: researchResult.text,
|
|
138
|
+
captureTarget,
|
|
120
139
|
};
|
|
121
140
|
}
|
|
122
141
|
const promptEcho = buildPromptEchoMatcher(deps.promptPreview);
|
|
@@ -124,8 +143,13 @@ export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
|
124
143
|
const recovered = await recoverPromptEcho(Runtime, answer, promptEcho, logger, minTurnIndex, timeoutMs);
|
|
125
144
|
const markdown = (await withTimeout(captureMarkdown(Runtime, recovered.meta, logger), 15_000, "Reattach markdown capture timed out")) ?? recovered.text;
|
|
126
145
|
const aligned = alignPromptEchoMarkdown(recovered.text, markdown, promptEcho, logger);
|
|
146
|
+
const captureTarget = await captureIdentity().catch(() => undefined);
|
|
127
147
|
await closeAttached();
|
|
128
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
answerText: aligned.answerText,
|
|
150
|
+
answerMarkdown: aligned.answerMarkdown,
|
|
151
|
+
captureTarget,
|
|
152
|
+
};
|
|
129
153
|
}
|
|
130
154
|
catch (error) {
|
|
131
155
|
await closeAttached();
|
|
@@ -216,7 +240,10 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
216
240
|
await DOM.enable();
|
|
217
241
|
}
|
|
218
242
|
if (!resolved.headless && resolved.hideWindow) {
|
|
219
|
-
await positionChromeWindowOffscreen(client, logger);
|
|
243
|
+
await positionChromeWindowOffscreen(client, userDataDir, logger);
|
|
244
|
+
}
|
|
245
|
+
else if (!resolved.headless) {
|
|
246
|
+
await positionChromeWindowOnscreen(client, userDataDir, logger);
|
|
220
247
|
}
|
|
221
248
|
let appliedCookies = 0;
|
|
222
249
|
if (shouldSyncBrowserCookies(resolved, { manualLogin })) {
|