pi-oracle 0.7.4 → 0.7.6
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/CHANGELOG.md +32 -0
- package/README.md +53 -18
- package/docs/ORACLE_DESIGN.md +16 -8
- package/docs/platform-smoke.md +156 -0
- package/extensions/oracle/index.ts +10 -4
- package/extensions/oracle/lib/config.ts +53 -27
- package/extensions/oracle/lib/jobs.ts +9 -5
- package/extensions/oracle/lib/poller.ts +1 -0
- package/extensions/oracle/lib/runtime.ts +107 -32
- package/extensions/oracle/lib/tools.ts +138 -12
- package/extensions/oracle/shared/browser-profile-helpers.d.mts +59 -0
- package/extensions/oracle/shared/browser-profile-helpers.mjs +395 -0
- package/extensions/oracle/shared/process-helpers.mjs +12 -1
- package/extensions/oracle/shared/state-coordination-helpers.mjs +8 -2
- package/extensions/oracle/worker/auth-bootstrap.mjs +39 -10
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +2 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +157 -1
- package/extensions/oracle/worker/chromium-cookie-source.mjs +2 -1
- package/extensions/oracle/worker/run-job.mjs +107 -25
- package/package.json +30 -9
- package/platform-smoke.config.mjs +66 -0
- package/scripts/oracle-real-smoke.mjs +500 -0
- package/scripts/platform-smoke/Dockerfile.ubuntu +8 -0
- package/scripts/platform-smoke/artifacts.mjs +87 -0
- package/scripts/platform-smoke/assertions.mjs +34 -0
- package/scripts/platform-smoke/crabbox-runner.mjs +135 -0
- package/scripts/platform-smoke/doctor.mjs +239 -0
- package/scripts/platform-smoke/invariants.mjs +124 -0
- package/scripts/platform-smoke/platform-build-windows.ps1 +168 -0
- package/scripts/platform-smoke/targets.mjs +434 -0
- package/scripts/platform-smoke.mjs +152 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Invariants/Assumptions: Job state is persisted under worker-held locks, browser/session artifacts live under the configured oracle directories, and cleanup preserves durable recovery semantics.
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
-
import { appendFile, chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
|
+
import { appendFile, chmod, cp as copyDirectory, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
9
9
|
import { basename, dirname, join } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
@@ -23,7 +23,9 @@ import { extractArtifactLabels, FILE_LABEL_PATTERN_SOURCE, GENERIC_ARTIFACT_LABE
|
|
|
23
23
|
import {
|
|
24
24
|
buildAllowedChatGptOrigins,
|
|
25
25
|
deriveAssistantCompletionSignature,
|
|
26
|
+
matchesCompactIntelligenceOpenerLabel,
|
|
26
27
|
matchesModelFamilyLabel,
|
|
28
|
+
matchesRequestedModelControlLabel,
|
|
27
29
|
requestedEffortLabel,
|
|
28
30
|
effortSelectionVisible,
|
|
29
31
|
snapshotCanSafelySkipModelConfiguration,
|
|
@@ -34,6 +36,7 @@ import {
|
|
|
34
36
|
autoSwitchToThinkingSelectionVisible,
|
|
35
37
|
} from "./chatgpt-ui-helpers.mjs";
|
|
36
38
|
import { assistantSnapshotSlice, nextStableValueState, resolveStableConversationUrlCandidate, stripUrlQueryAndHash } from "./chatgpt-flow-helpers.mjs";
|
|
39
|
+
import { assertNotKnownBrowserUserDataPath, scrubSweetCookieSafeStoragePasswordEnv, sweetCookieSafeStoragePasswordScrubbedEnv } from "../shared/browser-profile-helpers.mjs";
|
|
37
40
|
import { createLease, listLeaseMetadata, readLeaseMetadata, releaseLease, withLock } from "./state-locks.mjs";
|
|
38
41
|
|
|
39
42
|
const jobId = process.argv[2];
|
|
@@ -80,6 +83,8 @@ const POST_SEND_SETTLE_MS = 15_000;
|
|
|
80
83
|
const AGENT_BROWSER_BIN = [process.env.AGENT_BROWSER_PATH, "/opt/homebrew/bin/agent-browser", "/usr/local/bin/agent-browser"].find(
|
|
81
84
|
(candidate) => typeof candidate === "string" && candidate && existsSync(candidate),
|
|
82
85
|
) || "agent-browser";
|
|
86
|
+
const CP_BIN = process.env.PI_ORACLE_CP_PATH?.trim() || "cp";
|
|
87
|
+
scrubSweetCookieSafeStoragePasswordEnv();
|
|
83
88
|
|
|
84
89
|
let currentJob;
|
|
85
90
|
let browserStarted = false;
|
|
@@ -209,12 +214,30 @@ function sleep(ms) {
|
|
|
209
214
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
210
215
|
}
|
|
211
216
|
|
|
217
|
+
function killProcessTree(child) {
|
|
218
|
+
if (process.platform === "win32" && child.pid) {
|
|
219
|
+
spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore", windowsHide: true }).on("error", () => undefined);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
child.kill("SIGTERM");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function killProcess(child) {
|
|
226
|
+
if (process.platform === "win32" && child.pid) {
|
|
227
|
+
spawn("taskkill", ["/pid", String(child.pid), "/f"], { stdio: "ignore", windowsHide: true }).on("error", () => undefined);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
child.kill("SIGKILL");
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
function spawnCommand(command, args, options = {}) {
|
|
213
234
|
return new Promise((resolve, reject) => {
|
|
214
235
|
const { timeoutMs, ...spawnOptions } = options;
|
|
215
236
|
const child = spawn(command, args, {
|
|
216
237
|
stdio: ["pipe", "pipe", "pipe"],
|
|
217
238
|
...spawnOptions,
|
|
239
|
+
env: sweetCookieSafeStoragePasswordScrubbedEnv(spawnOptions.env),
|
|
240
|
+
shell: spawnOptions.shell ?? process.platform === "win32",
|
|
218
241
|
});
|
|
219
242
|
let stdout = "";
|
|
220
243
|
let stderr = "";
|
|
@@ -223,8 +246,8 @@ function spawnCommand(command, args, options = {}) {
|
|
|
223
246
|
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
224
247
|
killTimer = setTimeout(() => {
|
|
225
248
|
timedOut = true;
|
|
226
|
-
child
|
|
227
|
-
setTimeout(() => child
|
|
249
|
+
killProcessTree(child);
|
|
250
|
+
setTimeout(() => killProcess(child), 2_000).unref?.();
|
|
228
251
|
}, timeoutMs);
|
|
229
252
|
killTimer.unref?.();
|
|
230
253
|
}
|
|
@@ -274,8 +297,20 @@ async function removeChromiumProcessSingletonArtifacts(profileDir) {
|
|
|
274
297
|
]);
|
|
275
298
|
}
|
|
276
299
|
|
|
300
|
+
function assertSafeRuntimeProfilePath(path, label, config = undefined) {
|
|
301
|
+
try {
|
|
302
|
+
assertNotKnownBrowserUserDataPath(path, label, {
|
|
303
|
+
cookieSources: config ? { chromeProfile: config.auth.chromeProfile, chromeCookiePath: config.auth.chromeCookiePath } : undefined,
|
|
304
|
+
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
throw new Error(`Oracle ${label} path is unsafe: ${path}. ${error instanceof Error ? error.message : String(error)}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
277
310
|
async function cloneSeedProfileToRuntime(job) {
|
|
278
311
|
const seedDir = job.config.browser.authSeedProfileDir;
|
|
312
|
+
assertSafeRuntimeProfilePath(seedDir, "auth seed profile", job.config);
|
|
313
|
+
assertSafeRuntimeProfilePath(job.runtimeProfileDir, "runtime profile", job.config);
|
|
279
314
|
if (!existsSync(seedDir)) {
|
|
280
315
|
throw new Error(`Oracle auth seed profile not found: ${seedDir}. Run /oracle-auth first.`);
|
|
281
316
|
}
|
|
@@ -286,17 +321,17 @@ async function cloneSeedProfileToRuntime(job) {
|
|
|
286
321
|
await withLock(ORACLE_STATE_DIR, "auth", "global", { jobId: job.id, processPid: process.pid, action: "cloneSeedProfile" }, async () => {
|
|
287
322
|
await rm(job.runtimeProfileDir, { recursive: true, force: true }).catch(() => undefined);
|
|
288
323
|
await ensurePrivateDir(dirname(job.runtimeProfileDir));
|
|
289
|
-
if (job.config.browser.cloneStrategy === "apfs-clone") {
|
|
324
|
+
if (job.config.browser.cloneStrategy === "apfs-clone" && process.platform === "darwin") {
|
|
290
325
|
try {
|
|
291
|
-
await spawnCommand(
|
|
326
|
+
await spawnCommand(CP_BIN, ["-cR", seedDir, job.runtimeProfileDir], { timeoutMs: PROFILE_CLONE_TIMEOUT_MS });
|
|
292
327
|
} catch (error) {
|
|
293
328
|
const message = error instanceof Error ? error.message : String(error);
|
|
294
329
|
await log(`APFS clone copy failed; falling back to recursive copy: ${message}`);
|
|
295
330
|
await rm(job.runtimeProfileDir, { recursive: true, force: true }).catch(() => undefined);
|
|
296
|
-
await spawnCommand(
|
|
331
|
+
await spawnCommand(CP_BIN, ["-R", seedDir, job.runtimeProfileDir], { timeoutMs: PROFILE_CLONE_TIMEOUT_MS });
|
|
297
332
|
}
|
|
298
333
|
} else {
|
|
299
|
-
await
|
|
334
|
+
await copyDirectory(seedDir, job.runtimeProfileDir, { recursive: true, force: true, verbatimSymlinks: true });
|
|
300
335
|
}
|
|
301
336
|
await removeChromiumProcessSingletonArtifacts(job.runtimeProfileDir);
|
|
302
337
|
}, 10 * 60 * 1000);
|
|
@@ -314,27 +349,28 @@ async function cleanupRuntime(job) {
|
|
|
314
349
|
warnings.push(message);
|
|
315
350
|
await log(message).catch(() => undefined);
|
|
316
351
|
});
|
|
317
|
-
|
|
352
|
+
try {
|
|
353
|
+
assertSafeRuntimeProfilePath(job.runtimeProfileDir, "runtime profile", job.config);
|
|
354
|
+
await rm(job.runtimeProfileDir, { recursive: true, force: true });
|
|
355
|
+
} catch (error) {
|
|
318
356
|
const message = `Runtime profile cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
319
357
|
warnings.push(message);
|
|
320
358
|
await log(message).catch(() => undefined);
|
|
321
|
-
});
|
|
322
|
-
if (warnings.length === 0) {
|
|
323
|
-
await releaseLease(ORACLE_STATE_DIR, "conversation", job.conversationId).catch(async (error) => {
|
|
324
|
-
const message = `Conversation lease cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
325
|
-
warnings.push(message);
|
|
326
|
-
await log(message).catch(() => undefined);
|
|
327
|
-
});
|
|
328
|
-
await releaseLease(ORACLE_STATE_DIR, "runtime", job.runtimeId).catch(async (error) => {
|
|
329
|
-
const message = `Runtime lease cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
330
|
-
warnings.push(message);
|
|
331
|
-
await log(message).catch(() => undefined);
|
|
332
|
-
});
|
|
333
359
|
}
|
|
360
|
+
await releaseLease(ORACLE_STATE_DIR, "conversation", job.conversationId).catch(async (error) => {
|
|
361
|
+
const message = `Conversation lease cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
362
|
+
warnings.push(message);
|
|
363
|
+
await log(message).catch(() => undefined);
|
|
364
|
+
});
|
|
365
|
+
await releaseLease(ORACLE_STATE_DIR, "runtime", job.runtimeId).catch(async (error) => {
|
|
366
|
+
const message = `Runtime lease cleanup warning: ${error instanceof Error ? error.message : String(error)}`;
|
|
367
|
+
warnings.push(message);
|
|
368
|
+
await log(message).catch(() => undefined);
|
|
369
|
+
});
|
|
334
370
|
if (warnings.length === 0) {
|
|
335
371
|
await log(`Cleanup summary: runtime ${job.runtimeId} released with no warnings`).catch(() => undefined);
|
|
336
372
|
} else {
|
|
337
|
-
await log(`Cleanup summary: runtime ${job.runtimeId} released
|
|
373
|
+
await log(`Cleanup summary: runtime ${job.runtimeId} released after ${warnings.length} warning(s)`).catch(() => undefined);
|
|
338
374
|
}
|
|
339
375
|
return warnings;
|
|
340
376
|
} finally {
|
|
@@ -723,11 +759,42 @@ function matchesModelFamilyControl(candidate, family) {
|
|
|
723
759
|
return ["button", "radio", "menuitemradio"].includes(candidate.kind || "") && typeof candidate.label === "string" && matchesModelFamilyLabel(candidate.label, family) && !candidate.disabled;
|
|
724
760
|
}
|
|
725
761
|
|
|
762
|
+
function normalizeSnapshotLabel(value) {
|
|
763
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function snapshotHasLegacyEffortCombobox(snapshot) {
|
|
767
|
+
return Boolean(findEntry(snapshot, (candidate) => {
|
|
768
|
+
if (candidate.kind !== "combobox" || candidate.disabled) return false;
|
|
769
|
+
return /^(?:Thinking effort|Pro thinking effort)$/i.test(normalizeSnapshotLabel(candidate.label));
|
|
770
|
+
}));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function snapshotHasCompactIntelligenceMenuControls(snapshot) {
|
|
774
|
+
return Boolean(findEntry(snapshot, (candidate) => {
|
|
775
|
+
if (candidate.disabled) return false;
|
|
776
|
+
const label = normalizeSnapshotLabel(candidate.label);
|
|
777
|
+
return (candidate.kind === "menu" && /Intelligence.*Instant.*Medium.*High.*Pro/i.test(label))
|
|
778
|
+
|| (candidate.kind === "menuitemradio" && /^(?:Instant\s+5s|Medium\s+5\s*[–-]\s*30s|High\s+15\s*[–-]\s*60s|Pro\s+5\+\s*min)$/i.test(label));
|
|
779
|
+
}));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function matchesRequestedModelControl(candidate, selection, options = {}) {
|
|
783
|
+
if (!["button", "radio", "menuitemradio"].includes(candidate.kind || "") || typeof candidate.label !== "string" || candidate.disabled) return false;
|
|
784
|
+
if (candidate.kind === "button") {
|
|
785
|
+
if (/\bexpanded=true\b/.test(String(candidate.line || ""))) return false;
|
|
786
|
+
if (options.ignoreCompactTierButtons && /^(?:Instant|Medium|High|Pro)$/i.test(candidate.label)) return false;
|
|
787
|
+
if (options.ignoreCompactOnlyButtons && /^(?:Medium|High)$/i.test(candidate.label)) return false;
|
|
788
|
+
}
|
|
789
|
+
return matchesRequestedModelControlLabel(candidate.label, selection);
|
|
790
|
+
}
|
|
791
|
+
|
|
726
792
|
function matchesModelConfigurationOpener(candidate) {
|
|
727
793
|
if (candidate.kind !== "button" || typeof candidate.label !== "string" || candidate.disabled) return false;
|
|
728
794
|
const label = String(candidate.label || "");
|
|
729
795
|
return candidate.label === "Model"
|
|
730
796
|
|| candidate.label === "Model selector"
|
|
797
|
+
|| matchesCompactIntelligenceOpenerLabel(label)
|
|
731
798
|
|| /^(?:Light|Standard|Extended|Heavy)(?:, click to remove)?$/i.test(label)
|
|
732
799
|
|| ["instant", "thinking", "pro"].some((family) => matchesModelFamilyLabel(label, /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiModelFamily} */ (family)))
|
|
733
800
|
|| /^(?:(?:Light|Standard|Extended|Heavy) )?Thinking(?:, click to remove)?$/i.test(label)
|
|
@@ -1196,19 +1263,33 @@ async function configureModel(job) {
|
|
|
1196
1263
|
let verificationSnapshot = familySnapshot;
|
|
1197
1264
|
|
|
1198
1265
|
const alreadyConfiguredInUi = snapshotStronglyMatchesRequestedModel(familySnapshot, job.selection);
|
|
1199
|
-
|
|
1266
|
+
const legacyEffortComboboxVisible = snapshotHasLegacyEffortCombobox(familySnapshot);
|
|
1267
|
+
const familyAlreadySelectedInUi = !alreadyConfiguredInUi && legacyEffortComboboxVisible && snapshotWeaklyMatchesRequestedModel(familySnapshot, job.selection);
|
|
1268
|
+
const controlOptions = {
|
|
1269
|
+
ignoreCompactTierButtons: snapshotHasCompactIntelligenceMenuControls(familySnapshot),
|
|
1270
|
+
ignoreCompactOnlyButtons: legacyEffortComboboxVisible,
|
|
1271
|
+
};
|
|
1272
|
+
let familyEntry = alreadyConfiguredInUi || familyAlreadySelectedInUi
|
|
1273
|
+
? undefined
|
|
1274
|
+
: findEntry(familySnapshot, (candidate) => matchesRequestedModelControl(candidate, job.selection, controlOptions));
|
|
1200
1275
|
if (alreadyConfiguredInUi) {
|
|
1201
1276
|
await log("Model configuration UI opened with requested settings already selected");
|
|
1277
|
+
} else if (familyAlreadySelectedInUi) {
|
|
1278
|
+
await log("Model family already appears selected; verifying effort-specific settings");
|
|
1202
1279
|
} else if (!familyEntry) {
|
|
1203
1280
|
throw new Error(`Could not find model family control for ${job.selection.modelFamily}`);
|
|
1204
1281
|
}
|
|
1205
1282
|
|
|
1206
|
-
if (!alreadyConfiguredInUi && familyEntry) {
|
|
1283
|
+
if (!alreadyConfiguredInUi && !familyAlreadySelectedInUi && familyEntry) {
|
|
1207
1284
|
await clickRef(job, familyEntry.ref);
|
|
1208
1285
|
await agentBrowser(job, "wait", "800");
|
|
1209
1286
|
familySnapshot = await snapshotText(job);
|
|
1210
1287
|
verificationSnapshot = familySnapshot;
|
|
1211
|
-
|
|
1288
|
+
const postClickControlOptions = {
|
|
1289
|
+
ignoreCompactTierButtons: snapshotHasCompactIntelligenceMenuControls(familySnapshot),
|
|
1290
|
+
ignoreCompactOnlyButtons: snapshotHasLegacyEffortCombobox(familySnapshot),
|
|
1291
|
+
};
|
|
1292
|
+
familyEntry = findEntry(familySnapshot, (candidate) => matchesRequestedModelControl(candidate, job.selection, postClickControlOptions));
|
|
1212
1293
|
if (!familyEntry && !snapshotStronglyMatchesRequestedModel(familySnapshot, job.selection)) {
|
|
1213
1294
|
throw new Error(`Requested model family did not remain selected: ${job.selection.modelFamily}`);
|
|
1214
1295
|
}
|
|
@@ -1240,7 +1321,8 @@ async function configureModel(job) {
|
|
|
1240
1321
|
if (job.selection.modelFamily === "instant") {
|
|
1241
1322
|
const desiredAutoSwitchState = job.selection.autoSwitchToThinking === true;
|
|
1242
1323
|
const currentAutoSwitchState = autoSwitchToThinkingSelectionVisible(familySnapshot);
|
|
1243
|
-
|
|
1324
|
+
const compactInstantAlreadyVerified = desiredAutoSwitchState && currentAutoSwitchState === undefined && snapshotStronglyMatchesRequestedModel(familySnapshot, job.selection);
|
|
1325
|
+
if (!compactInstantAlreadyVerified && currentAutoSwitchState !== desiredAutoSwitchState && (desiredAutoSwitchState || currentAutoSwitchState === true)) {
|
|
1244
1326
|
await clickAutoSwitchToThinkingControl(job);
|
|
1245
1327
|
await agentBrowser(job, "wait", "400");
|
|
1246
1328
|
verificationSnapshot = await snapshotText(job);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-oracle",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "ChatGPT and Grok web-oracle extension for pi with isolated browser auth, async jobs, and project-context archives.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,11 @@
|
|
|
32
32
|
"docs",
|
|
33
33
|
"README.md",
|
|
34
34
|
"CHANGELOG.md",
|
|
35
|
-
"LICENSE"
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"platform-smoke.config.mjs",
|
|
37
|
+
"scripts/platform-smoke.mjs",
|
|
38
|
+
"scripts/platform-smoke",
|
|
39
|
+
"scripts/oracle-real-smoke.mjs"
|
|
36
40
|
],
|
|
37
41
|
"pi": {
|
|
38
42
|
"extensions": [
|
|
@@ -43,14 +47,29 @@
|
|
|
43
47
|
]
|
|
44
48
|
},
|
|
45
49
|
"scripts": {
|
|
46
|
-
"check:oracle-extension": "node --check extensions/oracle/shared/process-helpers.mjs && node --check extensions/oracle/shared/state-coordination-helpers.mjs && node --check extensions/oracle/shared/job-coordination-helpers.mjs && node --check extensions/oracle/shared/job-lifecycle-helpers.mjs && node --check extensions/oracle/shared/job-observability-helpers.mjs && node --check extensions/oracle/worker/run-job.mjs && node --check extensions/oracle/worker/state-locks.mjs && node --check extensions/oracle/worker/artifact-heuristics.mjs && node --check extensions/oracle/worker/chatgpt-ui-helpers.mjs && node --check extensions/oracle/worker/chatgpt-flow-helpers.mjs && node --check extensions/oracle/worker/auth-flow-helpers.mjs && node --check extensions/oracle/worker/auth-cookie-policy.mjs && node --check extensions/oracle/worker/chromium-cookie-source.mjs && node --check extensions/oracle/worker/auth-bootstrap.mjs && esbuild extensions/oracle/index.ts --bundle --platform=node --format=esm --external:@earendil-works/pi-coding-agent --external
|
|
50
|
+
"check:oracle-extension": "node --check extensions/oracle/shared/browser-profile-helpers.mjs && node --check extensions/oracle/shared/process-helpers.mjs && node --check extensions/oracle/shared/state-coordination-helpers.mjs && node --check extensions/oracle/shared/job-coordination-helpers.mjs && node --check extensions/oracle/shared/job-lifecycle-helpers.mjs && node --check extensions/oracle/shared/job-observability-helpers.mjs && node --check extensions/oracle/worker/run-job.mjs && node --check extensions/oracle/worker/state-locks.mjs && node --check extensions/oracle/worker/artifact-heuristics.mjs && node --check extensions/oracle/worker/chatgpt-ui-helpers.mjs && node --check extensions/oracle/worker/chatgpt-flow-helpers.mjs && node --check extensions/oracle/worker/auth-flow-helpers.mjs && node --check extensions/oracle/worker/auth-cookie-policy.mjs && node --check extensions/oracle/worker/chromium-cookie-source.mjs && node --check extensions/oracle/worker/auth-bootstrap.mjs && esbuild extensions/oracle/index.ts --bundle --platform=node --format=esm --external:@earendil-works/pi-coding-agent --external:typebox --outfile=/tmp/pi-oracle-extension-check.js",
|
|
47
51
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
52
|
"typecheck:worker-helpers": "tsc --noEmit -p tsconfig.worker-helpers.json",
|
|
49
53
|
"sanity:oracle": "node scripts/oracle-sanity-runner.mjs",
|
|
50
54
|
"pack:check": "npm pack --dry-run",
|
|
51
|
-
"verify:oracle": "npm run check:oracle-extension && npm run typecheck && npm run typecheck:worker-helpers && npm run sanity:oracle && npm run pack:check",
|
|
55
|
+
"verify:oracle": "npm run check:oracle-extension && npm run check:platform-smoke && npm run check:oracle-real-smoke && npm run typecheck && npm run typecheck:worker-helpers && npm run sanity:oracle && npm run pack:check",
|
|
52
56
|
"test": "npm run verify:oracle",
|
|
53
|
-
"prepublishOnly": "npm run
|
|
57
|
+
"prepublishOnly": "npm run release:check",
|
|
58
|
+
"check:platform-smoke": "node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/assertions.mjs && node --check scripts/platform-smoke/artifacts.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/targets.mjs && node scripts/platform-smoke/invariants.mjs",
|
|
59
|
+
"smoke:platform": "node scripts/platform-smoke.mjs",
|
|
60
|
+
"smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
|
|
61
|
+
"smoke:platform:ubuntu": "node scripts/platform-smoke.mjs run --target ubuntu",
|
|
62
|
+
"smoke:platform:all": "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native",
|
|
63
|
+
"smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
|
|
64
|
+
"smoke:platform:windows-native": "node scripts/platform-smoke.mjs run --target windows-native",
|
|
65
|
+
"smoke:real": "npm run smoke:real:packed",
|
|
66
|
+
"smoke:real:doctor": "node scripts/oracle-real-smoke.mjs doctor",
|
|
67
|
+
"release:check": "npm run verify:oracle && npm run smoke:platform:all",
|
|
68
|
+
"check:oracle-real-smoke": "node --check scripts/oracle-real-smoke.mjs",
|
|
69
|
+
"smoke:real:packed": "node scripts/oracle-real-smoke.mjs run --mode packed",
|
|
70
|
+
"smoke:real:source": "node scripts/oracle-real-smoke.mjs run --mode source",
|
|
71
|
+
"sanity:oracle:platform": "node scripts/oracle-sanity-runner.mjs --mode platform",
|
|
72
|
+
"verify:oracle:platform": "npm run check:oracle-extension && npm run check:platform-smoke && npm run check:oracle-real-smoke && npm run sanity:oracle:platform && npm run pack:check"
|
|
54
73
|
},
|
|
55
74
|
"dependencies": {
|
|
56
75
|
"@steipete/sweet-cookie": "^0.3.0"
|
|
@@ -64,9 +83,9 @@
|
|
|
64
83
|
"protobufjs": "7.6.1"
|
|
65
84
|
},
|
|
66
85
|
"devDependencies": {
|
|
67
|
-
"@earendil-works/pi-ai": "^0.
|
|
68
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
69
|
-
"@types/node": "^
|
|
86
|
+
"@earendil-works/pi-ai": "^0.78.1",
|
|
87
|
+
"@earendil-works/pi-coding-agent": "^0.78.1",
|
|
88
|
+
"@types/node": "^22.19.19",
|
|
70
89
|
"esbuild": "^0.28.0",
|
|
71
90
|
"tsx": "^4.22.3",
|
|
72
91
|
"typebox": "^1.1.39",
|
|
@@ -76,7 +95,9 @@
|
|
|
76
95
|
"node": ">=22.19.0"
|
|
77
96
|
},
|
|
78
97
|
"os": [
|
|
79
|
-
"darwin"
|
|
98
|
+
"darwin",
|
|
99
|
+
"linux",
|
|
100
|
+
"win32"
|
|
80
101
|
],
|
|
81
102
|
"packageManager": "npm@11.16.0",
|
|
82
103
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Platform smoke configuration for pi-oracle.
|
|
2
|
+
// Crabbox is used as the local cross-platform release/readiness gate.
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
packageName: "pi-oracle",
|
|
6
|
+
artifactRoot: ".artifacts/platform-smoke",
|
|
7
|
+
requiredTargets: ["macos", "ubuntu", "windows-native"],
|
|
8
|
+
requiredSuites: ["platform-build", "real-extension"],
|
|
9
|
+
workflows: {
|
|
10
|
+
everyday: {
|
|
11
|
+
description: "Fast local validation for normal iteration.",
|
|
12
|
+
commands: ["npm run verify:oracle"],
|
|
13
|
+
},
|
|
14
|
+
platformSensitive: {
|
|
15
|
+
description: "Doctor plus focused platform target/suite runs for platform-sensitive changes.",
|
|
16
|
+
commands: [
|
|
17
|
+
"npm run smoke:platform:doctor",
|
|
18
|
+
"node scripts/platform-smoke.mjs run --target <target> --suite <suite>",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
platformMatrix: {
|
|
22
|
+
description: "Doctor-first packed-install macOS/Ubuntu/Windows platform proof.",
|
|
23
|
+
commands: ["npm run smoke:platform:all"],
|
|
24
|
+
},
|
|
25
|
+
release: {
|
|
26
|
+
description: "Full release gate: local verification plus the doctor-first platform matrix.",
|
|
27
|
+
commands: ["npm run release:check"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
requiredCrabbox: {
|
|
31
|
+
source: "https://github.com/openclaw/crabbox",
|
|
32
|
+
minVersion: "0.26.0",
|
|
33
|
+
},
|
|
34
|
+
ubuntuContainerImage: "pi-oracle-platform-smoke:node24",
|
|
35
|
+
ubuntuContainerBaseImage: "cimg/node:24.16",
|
|
36
|
+
windowsParallels: {
|
|
37
|
+
sourceVm: "pi-extension-windows-template",
|
|
38
|
+
snapshot: "crabbox-ready",
|
|
39
|
+
},
|
|
40
|
+
nodeValidationMajor: 24,
|
|
41
|
+
realSmoke: {
|
|
42
|
+
defaultProvider: "zai",
|
|
43
|
+
defaultModel: "glm-5.1",
|
|
44
|
+
authEnvByProvider: {
|
|
45
|
+
zai: ["ZAI_API_KEY"],
|
|
46
|
+
openai: ["OPENAI_API_KEY"],
|
|
47
|
+
anthropic: ["ANTHROPIC_API_KEY", "ANTHROPIC_OAUTH_TOKEN"],
|
|
48
|
+
google: ["GEMINI_API_KEY"],
|
|
49
|
+
xai: ["XAI_API_KEY"],
|
|
50
|
+
groq: ["GROQ_API_KEY"],
|
|
51
|
+
deepseek: ["DEEPSEEK_API_KEY"],
|
|
52
|
+
cerebras: ["CEREBRAS_API_KEY"],
|
|
53
|
+
fireworks: ["FIREWORKS_API_KEY"],
|
|
54
|
+
together: ["TOGETHER_API_KEY"],
|
|
55
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
56
|
+
ai_gateway: ["AI_GATEWAY_API_KEY"],
|
|
57
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
58
|
+
minimax: ["MINIMAX_API_KEY"],
|
|
59
|
+
"minimax-cn": ["MINIMAX_CN_API_KEY"],
|
|
60
|
+
"ant-ling": ["ANT_LING_API_KEY"],
|
|
61
|
+
nvidia: ["NVIDIA_API_KEY"],
|
|
62
|
+
moonshot: ["MOONSHOT_API_KEY"],
|
|
63
|
+
kimi: ["KIMI_API_KEY"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|