@steipete/oracle 0.14.1 → 0.15.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 +16 -6
- package/dist/bin/oracle.js +569 -0
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +416 -0
- package/dist/docs-site/browser-mode.html +594 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +462 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +399 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +388 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +323 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/assistantResponse.js +40 -31
- package/dist/src/browser/actions/attachments.js +28 -1
- package/dist/src/browser/actions/deepResearch.js +344 -128
- package/dist/src/browser/actions/modelSelection.js +75 -9
- package/dist/src/browser/actions/promptComposer.js +71 -14
- package/dist/src/browser/actions/thinkingStatus.js +19 -1
- package/dist/src/browser/actions/thinkingTime.js +65 -20
- package/dist/src/browser/artifacts.js +193 -14
- package/dist/src/browser/chatgptFiles.js +674 -91
- package/dist/src/browser/chromeCookies.js +312 -0
- package/dist/src/browser/chromeLifecycle.js +35 -4
- package/dist/src/browser/constants.js +5 -0
- package/dist/src/browser/deepResearchResult.js +23 -0
- package/dist/src/browser/index.js +112 -41
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/profileCopy.js +93 -0
- package/dist/src/browser/sessionRunner.js +9 -3
- package/dist/src/browser/windowsCookies.js +219 -0
- package/dist/src/cli/bridge/client.js +4 -1
- package/dist/src/cli/bridge/doctor.js +19 -0
- package/dist/src/cli/browserConfig.js +17 -1
- package/dist/src/cli/runOptions.js +11 -2
- package/dist/src/cli/sessionDisplay.js +6 -1
- package/dist/src/cli/sessionRunner.js +41 -17
- package/dist/src/config.js +3 -0
- package/dist/src/oracle/client.js +2 -0
- package/dist/src/oracle/modelResolver.js +85 -0
- package/dist/src/oracle/multiModelRunner.js +4 -1
- package/dist/src/oracle/run.js +4 -1
- package/dist/src/remote/client.js +253 -22
- package/dist/src/remote/health.js +27 -0
- package/dist/src/remote/server.js +239 -4
- package/dist/src/remote/types.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +45 -43
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/vendor/oracle-notifier/README.md +26 -0
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
1
3
|
import fs from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { getOracleHomeDir } from "../oracleHome.js";
|
|
6
|
+
import { isDeepResearchIncompleteText } from "./deepResearchResult.js";
|
|
4
7
|
const ARTIFACTS_DIRNAME = "artifacts";
|
|
8
|
+
const ZIP_EOCD_SIGNATURE = 0x06054b50;
|
|
9
|
+
const ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
10
|
+
const ZIP_EMPTY_ARCHIVE_LENGTH = 22;
|
|
11
|
+
const ZIP_MAX_EOCD_COMMENT_BYTES = 65_535;
|
|
5
12
|
function sanitizePathSegment(value, fallback) {
|
|
6
13
|
const sanitized = value
|
|
7
14
|
.toLowerCase()
|
|
@@ -11,6 +18,29 @@ function sanitizePathSegment(value, fallback) {
|
|
|
11
18
|
.slice(0, 80);
|
|
12
19
|
return sanitized || fallback;
|
|
13
20
|
}
|
|
21
|
+
export function sanitizeArtifactFilename(value, fallback = "artifact.bin") {
|
|
22
|
+
const normalized = String(value ?? "")
|
|
23
|
+
.replace(/\0/g, "")
|
|
24
|
+
.replace(/\\/g, "/");
|
|
25
|
+
const basename = path.basename(normalized).replace(/\.crdownload$/i, "");
|
|
26
|
+
const fallbackName = path.basename(fallback.replace(/\\/g, "/")) || "artifact.bin";
|
|
27
|
+
const sanitized = sanitizePathSegment(basename, sanitizePathSegment(fallbackName, "artifact.bin"));
|
|
28
|
+
return sanitized === "." || sanitized === ".."
|
|
29
|
+
? sanitizePathSegment(fallbackName, "artifact.bin")
|
|
30
|
+
: sanitized;
|
|
31
|
+
}
|
|
32
|
+
export function sanitizeArtifactMimeType(value) {
|
|
33
|
+
const mime = String(value ?? "")
|
|
34
|
+
.split(";", 1)[0]
|
|
35
|
+
?.trim()
|
|
36
|
+
.toLowerCase();
|
|
37
|
+
if (!mime ||
|
|
38
|
+
mime.length > 127 ||
|
|
39
|
+
!/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/.test(mime)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return mime;
|
|
43
|
+
}
|
|
14
44
|
function normalizeSessionId(sessionId) {
|
|
15
45
|
return sanitizePathSegment(path.basename(sessionId), "session");
|
|
16
46
|
}
|
|
@@ -26,7 +56,7 @@ async function pathExists(targetPath) {
|
|
|
26
56
|
return false;
|
|
27
57
|
}
|
|
28
58
|
}
|
|
29
|
-
async function
|
|
59
|
+
export async function resolveUniqueArtifactPath(basePath) {
|
|
30
60
|
const ext = path.extname(basePath);
|
|
31
61
|
const stem = ext ? path.basename(basePath, ext) : path.basename(basePath);
|
|
32
62
|
const dir = path.dirname(basePath);
|
|
@@ -46,6 +76,138 @@ async function readSizeBytes(targetPath) {
|
|
|
46
76
|
return undefined;
|
|
47
77
|
}
|
|
48
78
|
}
|
|
79
|
+
export function computeBufferSha256(contents) {
|
|
80
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
81
|
+
}
|
|
82
|
+
export async function computeFileSha256(targetPath) {
|
|
83
|
+
const hash = createHash("sha256");
|
|
84
|
+
await new Promise((resolve, reject) => {
|
|
85
|
+
const stream = createReadStream(targetPath);
|
|
86
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
87
|
+
stream.on("error", reject);
|
|
88
|
+
stream.on("end", () => resolve());
|
|
89
|
+
});
|
|
90
|
+
return hash.digest("hex");
|
|
91
|
+
}
|
|
92
|
+
export function isZipArtifact(filename, mimeType) {
|
|
93
|
+
const ext = path.extname(String(filename ?? "")).toLowerCase();
|
|
94
|
+
const mime = String(mimeType ?? "").toLowerCase();
|
|
95
|
+
return (ext === ".zip" ||
|
|
96
|
+
mime === "application/zip" ||
|
|
97
|
+
mime === "application/x-zip-compressed" ||
|
|
98
|
+
mime.endsWith("+zip"));
|
|
99
|
+
}
|
|
100
|
+
export function validateZipBuffer(contents) {
|
|
101
|
+
if (contents.length < ZIP_EMPTY_ARCHIVE_LENGTH) {
|
|
102
|
+
return { type: "zip", ok: false, error: "zip-too-small" };
|
|
103
|
+
}
|
|
104
|
+
const firstSignature = contents.readUInt32LE(0);
|
|
105
|
+
if (firstSignature !== ZIP_LOCAL_FILE_HEADER_SIGNATURE && firstSignature !== ZIP_EOCD_SIGNATURE) {
|
|
106
|
+
return { type: "zip", ok: false, error: "zip-magic-mismatch" };
|
|
107
|
+
}
|
|
108
|
+
const searchStart = Math.max(0, contents.length - ZIP_EMPTY_ARCHIVE_LENGTH - ZIP_MAX_EOCD_COMMENT_BYTES);
|
|
109
|
+
let eocdOffset = -1;
|
|
110
|
+
for (let offset = contents.length - ZIP_EMPTY_ARCHIVE_LENGTH; offset >= searchStart; offset -= 1) {
|
|
111
|
+
if (contents.readUInt32LE(offset) === ZIP_EOCD_SIGNATURE) {
|
|
112
|
+
eocdOffset = offset;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (eocdOffset < 0) {
|
|
117
|
+
return { type: "zip", ok: false, error: "zip-central-directory-missing" };
|
|
118
|
+
}
|
|
119
|
+
const commentLength = contents.readUInt16LE(eocdOffset + 20);
|
|
120
|
+
if (eocdOffset + ZIP_EMPTY_ARCHIVE_LENGTH + commentLength !== contents.length) {
|
|
121
|
+
return { type: "zip", ok: false, error: "zip-eocd-size-mismatch" };
|
|
122
|
+
}
|
|
123
|
+
const centralDirectorySize = contents.readUInt32LE(eocdOffset + 12);
|
|
124
|
+
const centralDirectoryOffset = contents.readUInt32LE(eocdOffset + 16);
|
|
125
|
+
if (centralDirectoryOffset + centralDirectorySize > eocdOffset) {
|
|
126
|
+
return { type: "zip", ok: false, error: "zip-central-directory-out-of-range" };
|
|
127
|
+
}
|
|
128
|
+
return { type: "zip", ok: true };
|
|
129
|
+
}
|
|
130
|
+
async function readFileWindow(handle, length, position) {
|
|
131
|
+
const buffer = Buffer.alloc(length);
|
|
132
|
+
let offset = 0;
|
|
133
|
+
while (offset < length) {
|
|
134
|
+
const result = await handle.read(buffer, offset, length - offset, position + offset);
|
|
135
|
+
if (result.bytesRead === 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
offset += result.bytesRead;
|
|
139
|
+
}
|
|
140
|
+
return buffer;
|
|
141
|
+
}
|
|
142
|
+
export async function validateZipFile(targetPath) {
|
|
143
|
+
const handle = await fs.open(targetPath, "r");
|
|
144
|
+
try {
|
|
145
|
+
const fileStat = await handle.stat();
|
|
146
|
+
if (fileStat.size < ZIP_EMPTY_ARCHIVE_LENGTH) {
|
|
147
|
+
return { type: "zip", ok: false, error: "zip-too-small" };
|
|
148
|
+
}
|
|
149
|
+
const first = await readFileWindow(handle, 4, 0);
|
|
150
|
+
if (!first) {
|
|
151
|
+
return { type: "zip", ok: false, error: "zip-too-small" };
|
|
152
|
+
}
|
|
153
|
+
const firstSignature = first.readUInt32LE(0);
|
|
154
|
+
if (firstSignature !== ZIP_LOCAL_FILE_HEADER_SIGNATURE &&
|
|
155
|
+
firstSignature !== ZIP_EOCD_SIGNATURE) {
|
|
156
|
+
return { type: "zip", ok: false, error: "zip-magic-mismatch" };
|
|
157
|
+
}
|
|
158
|
+
const tailLength = Math.min(fileStat.size, ZIP_EMPTY_ARCHIVE_LENGTH + ZIP_MAX_EOCD_COMMENT_BYTES);
|
|
159
|
+
const tailStart = fileStat.size - tailLength;
|
|
160
|
+
const tail = await readFileWindow(handle, tailLength, tailStart);
|
|
161
|
+
if (!tail) {
|
|
162
|
+
return { type: "zip", ok: false, error: "zip-central-directory-missing" };
|
|
163
|
+
}
|
|
164
|
+
let relativeEocdOffset = -1;
|
|
165
|
+
for (let offset = tail.length - ZIP_EMPTY_ARCHIVE_LENGTH; offset >= 0; offset -= 1) {
|
|
166
|
+
if (tail.readUInt32LE(offset) === ZIP_EOCD_SIGNATURE) {
|
|
167
|
+
relativeEocdOffset = offset;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (relativeEocdOffset < 0) {
|
|
172
|
+
return { type: "zip", ok: false, error: "zip-central-directory-missing" };
|
|
173
|
+
}
|
|
174
|
+
const eocdOffset = tailStart + relativeEocdOffset;
|
|
175
|
+
const commentLength = tail.readUInt16LE(relativeEocdOffset + 20);
|
|
176
|
+
if (eocdOffset + ZIP_EMPTY_ARCHIVE_LENGTH + commentLength !== fileStat.size) {
|
|
177
|
+
return { type: "zip", ok: false, error: "zip-eocd-size-mismatch" };
|
|
178
|
+
}
|
|
179
|
+
const centralDirectorySize = tail.readUInt32LE(relativeEocdOffset + 12);
|
|
180
|
+
const centralDirectoryOffset = tail.readUInt32LE(relativeEocdOffset + 16);
|
|
181
|
+
if (centralDirectoryOffset + centralDirectorySize > eocdOffset) {
|
|
182
|
+
return { type: "zip", ok: false, error: "zip-central-directory-out-of-range" };
|
|
183
|
+
}
|
|
184
|
+
return { type: "zip", ok: true };
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await handle.close();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export function validateArtifactBuffer(params) {
|
|
191
|
+
if (isZipArtifact(params.filename, params.mimeType)) {
|
|
192
|
+
return validateZipBuffer(params.contents);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
type: "generic",
|
|
196
|
+
ok: params.contents.length > 0,
|
|
197
|
+
error: params.contents.length > 0 ? undefined : "empty-file",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export async function validateArtifactFile(params) {
|
|
201
|
+
if (isZipArtifact(params.filename ?? path.basename(params.path), params.mimeType)) {
|
|
202
|
+
return validateZipFile(params.path);
|
|
203
|
+
}
|
|
204
|
+
const size = await readSizeBytes(params.path);
|
|
205
|
+
return {
|
|
206
|
+
type: "generic",
|
|
207
|
+
ok: Boolean(size && size > 0),
|
|
208
|
+
error: size && size > 0 ? undefined : "empty-file",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
49
211
|
export async function writeTextBrowserArtifact(params) {
|
|
50
212
|
const text = params.contents.trim();
|
|
51
213
|
if (!params.sessionId || text.length === 0) {
|
|
@@ -53,8 +215,8 @@ export async function writeTextBrowserArtifact(params) {
|
|
|
53
215
|
}
|
|
54
216
|
const dir = resolveSessionArtifactsDir(params.sessionId);
|
|
55
217
|
await fs.mkdir(dir, { recursive: true });
|
|
56
|
-
const filename =
|
|
57
|
-
const targetPath = await
|
|
218
|
+
const filename = sanitizeArtifactFilename(params.filename, "artifact.md");
|
|
219
|
+
const targetPath = await resolveUniqueArtifactPath(path.join(dir, filename));
|
|
58
220
|
await fs.writeFile(targetPath, `${text}\n`, "utf8");
|
|
59
221
|
params.logger?.(`[browser] Saved ${params.kind} artifact to ${targetPath}`);
|
|
60
222
|
return {
|
|
@@ -64,6 +226,10 @@ export async function writeTextBrowserArtifact(params) {
|
|
|
64
226
|
mimeType: params.mimeType ?? "text/markdown",
|
|
65
227
|
sizeBytes: await readSizeBytes(targetPath),
|
|
66
228
|
sourceUrl: params.sourceUrl,
|
|
229
|
+
sha256: computeBufferSha256(Buffer.from(`${text}\n`, "utf8")),
|
|
230
|
+
validation: { type: "generic", ok: true },
|
|
231
|
+
transfer: { status: "not-needed" },
|
|
232
|
+
origin: { mode: "local" },
|
|
67
233
|
};
|
|
68
234
|
}
|
|
69
235
|
export async function writeBinaryBrowserArtifact(params) {
|
|
@@ -72,10 +238,18 @@ export async function writeBinaryBrowserArtifact(params) {
|
|
|
72
238
|
}
|
|
73
239
|
const dir = resolveSessionArtifactsDir(params.sessionId);
|
|
74
240
|
await fs.mkdir(dir, { recursive: true });
|
|
75
|
-
const filename =
|
|
76
|
-
const targetPath = await
|
|
241
|
+
const filename = sanitizeArtifactFilename(params.filename, "artifact.bin");
|
|
242
|
+
const targetPath = await resolveUniqueArtifactPath(path.join(dir, filename));
|
|
77
243
|
await fs.writeFile(targetPath, params.contents);
|
|
244
|
+
const validation = validateArtifactBuffer({
|
|
245
|
+
filename,
|
|
246
|
+
mimeType: params.mimeType,
|
|
247
|
+
contents: params.contents,
|
|
248
|
+
});
|
|
78
249
|
params.logger?.(`[browser] Saved ${params.kind} artifact to ${targetPath}`);
|
|
250
|
+
if (validation.type === "zip" && !validation.ok) {
|
|
251
|
+
params.logger?.(`[browser] ZIP validation failed for ${filename}: ${validation.error ?? "invalid"}`);
|
|
252
|
+
}
|
|
79
253
|
return {
|
|
80
254
|
kind: params.kind,
|
|
81
255
|
path: targetPath,
|
|
@@ -83,18 +257,15 @@ export async function writeBinaryBrowserArtifact(params) {
|
|
|
83
257
|
mimeType: params.mimeType,
|
|
84
258
|
sizeBytes: params.contents.length,
|
|
85
259
|
sourceUrl: params.sourceUrl,
|
|
260
|
+
sha256: computeBufferSha256(params.contents),
|
|
261
|
+
validation,
|
|
262
|
+
transfer: { status: "not-needed" },
|
|
263
|
+
origin: { mode: "local" },
|
|
86
264
|
};
|
|
87
265
|
}
|
|
88
|
-
function isToolOnlyPlaceholder(text) {
|
|
89
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
90
|
-
return (normalized === "called tool" ||
|
|
91
|
-
normalized === "used tool" ||
|
|
92
|
-
normalized === "użyto narzędzia" ||
|
|
93
|
-
normalized === "narzędzie wywołane");
|
|
94
|
-
}
|
|
95
266
|
export async function saveDeepResearchReportArtifact(params) {
|
|
96
267
|
const report = params.reportMarkdown.trim();
|
|
97
|
-
if (report.length < 40 ||
|
|
268
|
+
if (report.length < 40 || isDeepResearchIncompleteText(report)) {
|
|
98
269
|
return null;
|
|
99
270
|
}
|
|
100
271
|
return writeTextBrowserArtifact({
|
|
@@ -120,7 +291,14 @@ export async function saveBrowserTranscriptArtifact(params) {
|
|
|
120
291
|
"",
|
|
121
292
|
...params.artifacts.map((artifact) => {
|
|
122
293
|
const label = artifact.label ?? artifact.kind;
|
|
123
|
-
|
|
294
|
+
const hash = artifact.sha256 ? ` sha256=${artifact.sha256}` : "";
|
|
295
|
+
const transfer = artifact.transfer?.status
|
|
296
|
+
? ` transfer=${artifact.transfer.status}`
|
|
297
|
+
: "";
|
|
298
|
+
const validation = artifact.validation
|
|
299
|
+
? ` validation=${artifact.validation.ok ? "ok" : (artifact.validation.error ?? "failed")}`
|
|
300
|
+
: "";
|
|
301
|
+
return `- ${label}: ${artifact.path}${hash}${transfer}${validation}`;
|
|
124
302
|
}),
|
|
125
303
|
]
|
|
126
304
|
: [];
|
|
@@ -165,5 +343,6 @@ export function appendArtifacts(existing, additions) {
|
|
|
165
343
|
}
|
|
166
344
|
export const __test__ = {
|
|
167
345
|
normalizeSessionId,
|
|
346
|
+
sanitizeArtifactFilename,
|
|
168
347
|
sanitizePathSegment,
|
|
169
348
|
};
|