pi-oracle 0.7.4 → 0.7.5
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 +18 -0
- package/README.md +51 -17
- package/docs/ORACLE_DESIGN.md +12 -5
- package/docs/platform-smoke.md +153 -0
- package/extensions/oracle/lib/config.ts +53 -27
- package/extensions/oracle/lib/jobs.ts +9 -5
- 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 +27 -6
- package/platform-smoke.config.mjs +59 -0
- package/scripts/oracle-real-smoke.mjs +497 -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 +108 -0
- package/scripts/platform-smoke/platform-build-windows.ps1 +168 -0
- package/scripts/platform-smoke/targets.mjs +434 -0
- package/scripts/platform-smoke.mjs +149 -0
|
@@ -29,11 +29,15 @@ const AUTO_SWITCH_LABEL = "Auto-switch to Thinking";
|
|
|
29
29
|
const THINKING_EFFORT_COMBOBOX_LABEL = "Thinking effort";
|
|
30
30
|
const PRO_THINKING_EFFORT_COMBOBOX_LABEL = "Pro thinking effort";
|
|
31
31
|
const EFFORT_LABELS = new Set(["Light", "Standard", "Extended", "Heavy"]);
|
|
32
|
+
const COMPACT_INTELLIGENCE_MENU_PATTERN = /Intelligence.*Instant.*Medium.*High.*Pro/i;
|
|
33
|
+
const COMPACT_INTELLIGENCE_CONTROL_PATTERN = /^(?:Instant(?:\s+5s)?|Medium(?:\s+5\s*[–-]\s*30s)?|High(?:\s+15\s*[–-]\s*60s)?|Pro(?:\s+5\+\s*min)?)$/i;
|
|
34
|
+
const COMPACT_INTELLIGENCE_OPENER_PATTERN = /^(?:Instant|Medium|High|Pro)$/i;
|
|
32
35
|
const BARE_EFFORT_PATTERN = /^(light|standard|extended|heavy)(?:, click to remove)?$/i;
|
|
33
36
|
const INSTANT_CHIP_PATTERN = /^instant(?:, click to remove)?$/i;
|
|
34
37
|
const THINKING_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?thinking(?:, click to remove)?$/i;
|
|
35
38
|
const PRO_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?pro(?:, click to remove)?$/i;
|
|
36
39
|
const MODEL_FAMILY_CONTROL_KINDS = new Set(["button", "radio", "menuitemradio"]);
|
|
40
|
+
const COMPACT_INTELLIGENCE_CONTROL_KINDS = new Set(["menuitemradio"]);
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* @param {string | undefined} url
|
|
@@ -151,9 +155,134 @@ function parseComposerChipSelection(label) {
|
|
|
151
155
|
return undefined;
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
function parseCompactIntelligenceSelection(label) {
|
|
159
|
+
if (/click to remove/i.test(String(label || ""))) return undefined;
|
|
160
|
+
const normalized = normalizeChipLabel(label);
|
|
161
|
+
if (!COMPACT_INTELLIGENCE_CONTROL_PATTERN.test(normalized)) return undefined;
|
|
162
|
+
|
|
163
|
+
if (/^Instant(?:\s+5s)?$/i.test(normalized)) {
|
|
164
|
+
return {
|
|
165
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("instant"),
|
|
166
|
+
compactTier: "instant",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (/^Medium(?:\s+5\s*[–-]\s*30s)?$/i.test(normalized)) {
|
|
170
|
+
return {
|
|
171
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("thinking"),
|
|
172
|
+
effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ ("standard"),
|
|
173
|
+
compactTier: "medium",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (/^High(?:\s+15\s*[–-]\s*60s)?$/i.test(normalized)) {
|
|
177
|
+
return {
|
|
178
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("thinking"),
|
|
179
|
+
effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ ("extended"),
|
|
180
|
+
compactTier: "high",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (/^Pro(?:\s+5\+\s*min)?$/i.test(normalized)) {
|
|
184
|
+
return {
|
|
185
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("pro"),
|
|
186
|
+
compactTier: "pro",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function hasRemovableComposerModelChip(entries) {
|
|
194
|
+
return entries.some(
|
|
195
|
+
(entry) => entry.kind === "button" && /click to remove/i.test(String(entry.label || "")) && parseComposerChipSelection(entry.label),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function hasCompactIntelligenceMenuContext(entries) {
|
|
200
|
+
return entries.some((entry) => !entry.disabled && entry.kind === "menu" && COMPACT_INTELLIGENCE_MENU_PATTERN.test(normalizeText(entry.label)))
|
|
201
|
+
|| entries.some((entry) => !entry.disabled && entry.kind === "menuitemradio" && checkedState(entry) === true && /\d/.test(String(entry.label || "")) && parseCompactIntelligenceSelection(entry.label));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hasLegacyEffortCombobox(entries) {
|
|
205
|
+
return entries.some((entry) => {
|
|
206
|
+
if (entry.disabled || entry.kind !== "combobox") return false;
|
|
207
|
+
const label = normalizeText(entry.label).toLowerCase();
|
|
208
|
+
return label === THINKING_EFFORT_COMBOBOX_LABEL.toLowerCase() || label === PRO_THINKING_EFFORT_COMBOBOX_LABEL.toLowerCase();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function compactSelectionFromEntry(entry, _entries, _options = {}) {
|
|
213
|
+
if (entry.disabled || !COMPACT_INTELLIGENCE_CONTROL_KINDS.has(entry.kind || "")) return undefined;
|
|
214
|
+
if (!/\d/.test(String(entry.label || ""))) return undefined;
|
|
215
|
+
return parseCompactIntelligenceSelection(entry.label);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function compactSelectionMatchesRequested(selection, compactSelection) {
|
|
219
|
+
if (!compactSelection || compactSelection.modelFamily !== selection.modelFamily) return false;
|
|
220
|
+
|
|
221
|
+
if (selection.modelFamily === "instant") {
|
|
222
|
+
// The compact Intelligence picker has no explicit auto-switch toggle. Treat
|
|
223
|
+
// Instant 5s as the closest available target for both instant presets.
|
|
224
|
+
return compactSelection.compactTier === "instant";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (selection.modelFamily === "pro") {
|
|
228
|
+
// The compact picker exposes one Pro tier instead of separate Pro efforts.
|
|
229
|
+
return compactSelection.compactTier === "pro";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (selection.modelFamily === "thinking") {
|
|
233
|
+
const requestedEffort = selection.effort || "standard";
|
|
234
|
+
if (compactSelection.compactTier === "medium") return requestedEffort === "light" || requestedEffort === "standard";
|
|
235
|
+
if (compactSelection.compactTier === "high") return requestedEffort === "extended" || requestedEffort === "heavy";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function compactSelectionMatchesRequestedInSnapshot(snapshot, selection, compactSelection, { weak = false } = {}) {
|
|
242
|
+
if (!compactSelectionMatchesRequested(selection, compactSelection)) return false;
|
|
243
|
+
if (selection.modelFamily !== "instant") return true;
|
|
244
|
+
|
|
245
|
+
const autoSwitchState = autoSwitchToThinkingSelectionVisible(snapshot);
|
|
246
|
+
if (autoSwitchState === undefined) return true;
|
|
247
|
+
if (weak) return selection.autoSwitchToThinking ? autoSwitchState !== false : autoSwitchState !== true;
|
|
248
|
+
return selection.autoSwitchToThinking ? autoSwitchState === true : autoSwitchState !== true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function detectCompactIntelligenceSelection(entries) {
|
|
252
|
+
if (hasRemovableComposerModelChip(entries)) return undefined;
|
|
253
|
+
if (hasLegacyEffortCombobox(entries)) return undefined;
|
|
254
|
+
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
if (entry.kind !== "menuitemradio" || checkedState(entry) !== true) continue;
|
|
257
|
+
const compactSelection = compactSelectionFromEntry(entry, entries, { allowClosedButtons: false });
|
|
258
|
+
if (compactSelection) return compactSelection;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (hasCompactIntelligenceMenuContext(entries)) return undefined;
|
|
262
|
+
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
if (entry.kind !== "button") continue;
|
|
265
|
+
const compactSelection = compactSelectionFromEntry(entry, entries);
|
|
266
|
+
if (!compactSelection) continue;
|
|
267
|
+
return compactSelection;
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function matchesRequestedModelControlLabel(label, selection) {
|
|
273
|
+
const compactSelection = parseCompactIntelligenceSelection(label);
|
|
274
|
+
if (compactSelection) return compactSelectionMatchesRequested(selection, compactSelection);
|
|
275
|
+
return matchesModelFamilyLabel(label, selection.modelFamily);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function matchesCompactIntelligenceOpenerLabel(label) {
|
|
279
|
+
return COMPACT_INTELLIGENCE_OPENER_PATTERN.test(normalizeChipLabel(label));
|
|
280
|
+
}
|
|
281
|
+
|
|
154
282
|
function detectComposerChipSelection(entries) {
|
|
155
283
|
for (const entry of entries) {
|
|
156
284
|
if (entry.disabled || entry.kind !== "button") continue;
|
|
285
|
+
if (/\bexpanded=true\b/.test(String(entry.line || "")) && !/click to remove/i.test(String(entry.label || ""))) continue;
|
|
157
286
|
const selection = parseComposerChipSelection(entry.label);
|
|
158
287
|
if (selection) return selection;
|
|
159
288
|
}
|
|
@@ -168,6 +297,9 @@ function checkedState(entry) {
|
|
|
168
297
|
}
|
|
169
298
|
|
|
170
299
|
function detectSelectedModelFamily(entries) {
|
|
300
|
+
const compactSelection = detectCompactIntelligenceSelection(entries);
|
|
301
|
+
if (compactSelection) return compactSelection.modelFamily;
|
|
302
|
+
|
|
171
303
|
for (const entry of entries) {
|
|
172
304
|
if (entry.disabled || !MODEL_FAMILY_CONTROL_KINDS.has(entry.kind || "") || checkedState(entry) !== true) continue;
|
|
173
305
|
for (const family of /** @type {OracleUiModelFamily[]} */ (["instant", "thinking", "pro"])) {
|
|
@@ -213,8 +345,15 @@ export function effortSelectionVisible(snapshot, effortLabel) {
|
|
|
213
345
|
/** @type {SnapshotEntry[]} */
|
|
214
346
|
const entries = parseSnapshotEntries(snapshot);
|
|
215
347
|
const normalizedEffort = effortLabel.toLowerCase();
|
|
348
|
+
const compactClosedButtonsAllowed = !hasRemovableComposerModelChip(entries) && !hasLegacyEffortCombobox(entries) && !hasCompactIntelligenceMenuContext(entries);
|
|
216
349
|
return entries.some((entry) => {
|
|
217
350
|
if (entry.disabled) return false;
|
|
351
|
+
const compactSelection = compactSelectionFromEntry(entry, entries, { allowClosedButtons: compactClosedButtonsAllowed });
|
|
352
|
+
if (compactSelection && entry.kind === "menuitemradio" && checkedState(entry) !== true) return false;
|
|
353
|
+
if (compactSelection?.modelFamily === "thinking") {
|
|
354
|
+
return compactSelectionMatchesRequested({ modelFamily: "thinking", effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ (normalizedEffort), autoSwitchToThinking: false }, compactSelection);
|
|
355
|
+
}
|
|
356
|
+
if (compactSelection?.modelFamily === "pro") return true;
|
|
218
357
|
if (entry.kind === "combobox" && normalizeText(entry.value).toLowerCase() === normalizedEffort) return true;
|
|
219
358
|
const chipSelection = entry.kind === "button" ? parseComposerChipSelection(entry.label) : undefined;
|
|
220
359
|
if (chipSelection?.effort === normalizedEffort) return true;
|
|
@@ -255,12 +394,18 @@ export function snapshotHasModelConfigurationUi(snapshot) {
|
|
|
255
394
|
.filter((family) => matchesModelFamilyLabel(entry.label, family)),
|
|
256
395
|
),
|
|
257
396
|
);
|
|
397
|
+
const visibleCompactControls = entries.filter(
|
|
398
|
+
(entry) => !entry.disabled && entry.kind === "menuitemradio" && /\d/.test(String(entry.label || "")) && parseCompactIntelligenceSelection(entry.label),
|
|
399
|
+
);
|
|
400
|
+
const hasCompactIntelligenceMenu = entries.some(
|
|
401
|
+
(entry) => !entry.disabled && entry.kind === "menu" && COMPACT_INTELLIGENCE_MENU_PATTERN.test(normalizeText(entry.label)),
|
|
402
|
+
);
|
|
258
403
|
const hasCloseButton = entries.some((entry) => entry.kind === "button" && entry.label === "Close" && !entry.disabled);
|
|
259
404
|
const hasIntelligenceHeading = entries.some((entry) => entry.kind === "heading" && normalizeText(entry.label) === "Intelligence" && !entry.disabled);
|
|
260
405
|
const hasEffortCombobox = entries.some(
|
|
261
406
|
(entry) => entry.kind === "combobox" && EFFORT_LABELS.has(entry.value || "") && !entry.disabled,
|
|
262
407
|
);
|
|
263
|
-
return visibleFamilies.size >= 2 || visibleRadioFamilies.size >= 2 || hasCloseButton || hasIntelligenceHeading || hasEffortCombobox;
|
|
408
|
+
return visibleFamilies.size >= 2 || visibleRadioFamilies.size >= 2 || visibleCompactControls.length >= 2 || hasCompactIntelligenceMenu || hasCloseButton || hasIntelligenceHeading || hasEffortCombobox;
|
|
264
409
|
}
|
|
265
410
|
|
|
266
411
|
/**
|
|
@@ -287,6 +432,7 @@ export function snapshotHasModelOpener(snapshot) {
|
|
|
287
432
|
const label = normalizeChipLabel(entry.label);
|
|
288
433
|
return label === "Model"
|
|
289
434
|
|| label === "Model selector"
|
|
435
|
+
|| COMPACT_INTELLIGENCE_OPENER_PATTERN.test(label)
|
|
290
436
|
|| EFFORT_LABELS.has(label)
|
|
291
437
|
|| ["instant", "thinking", "pro"].some((family) => matchesModelFamilyLabel(label, /** @type {OracleUiModelFamily} */ (family)))
|
|
292
438
|
|| THINKING_CHIP_PATTERN.test(label)
|
|
@@ -325,6 +471,10 @@ export function autoSwitchToThinkingSelectionVisible(snapshot) {
|
|
|
325
471
|
*/
|
|
326
472
|
export function snapshotCanSafelySkipModelConfiguration(snapshot, selection) {
|
|
327
473
|
if (!snapshotStronglyMatchesRequestedModel(snapshot, selection)) return false;
|
|
474
|
+
const hasBareProPill = selection.modelFamily === "pro" && parseSnapshotEntries(snapshot).some(
|
|
475
|
+
(entry) => entry.kind === "button" && !entry.disabled && normalizeChipLabel(entry.label) === "Pro",
|
|
476
|
+
);
|
|
477
|
+
if (hasBareProPill && !snapshotHasModelConfigurationUi(snapshot)) return false;
|
|
328
478
|
if (selection.modelFamily === "instant" && selection.autoSwitchToThinking) {
|
|
329
479
|
return autoSwitchToThinkingSelectionVisible(snapshot) === true;
|
|
330
480
|
}
|
|
@@ -339,6 +489,9 @@ export function snapshotCanSafelySkipModelConfiguration(snapshot, selection) {
|
|
|
339
489
|
export function snapshotStronglyMatchesRequestedModel(snapshot, selection) {
|
|
340
490
|
/** @type {SnapshotEntry[]} */
|
|
341
491
|
const entries = parseSnapshotEntries(snapshot);
|
|
492
|
+
const compactSelection = detectCompactIntelligenceSelection(entries);
|
|
493
|
+
if (compactSelection) return compactSelectionMatchesRequestedInSnapshot(snapshot, selection, compactSelection);
|
|
494
|
+
|
|
342
495
|
const chipSelection = detectComposerChipSelection(entries);
|
|
343
496
|
if (chipSelection) return selectionMatchesChipSelection(selection, chipSelection);
|
|
344
497
|
|
|
@@ -366,6 +519,9 @@ export function snapshotStronglyMatchesRequestedModel(snapshot, selection) {
|
|
|
366
519
|
export function snapshotWeaklyMatchesRequestedModel(snapshot, selection) {
|
|
367
520
|
/** @type {SnapshotEntry[]} */
|
|
368
521
|
const entries = parseSnapshotEntries(snapshot);
|
|
522
|
+
const compactSelection = detectCompactIntelligenceSelection(entries);
|
|
523
|
+
if (compactSelection) return compactSelectionMatchesRequestedInSnapshot(snapshot, selection, compactSelection, { weak: true });
|
|
524
|
+
|
|
369
525
|
const chipSelection = detectComposerChipSelection(entries);
|
|
370
526
|
if (chipSelection) return selectionMatchesChipSelection(selection, chipSelection);
|
|
371
527
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Usage: auth-bootstrap.mjs uses this when auth.chromiumKeychain is configured alongside auth.chromeCookiePath.
|
|
5
5
|
// Invariants/Assumptions: The configured cookie path points at a Chromium Cookies DB and the configured Keychain item is the browser's safe-storage secret.
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { sweetCookieSafeStoragePasswordScrubbedEnv } from "../shared/browser-profile-helpers.mjs";
|
|
7
8
|
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
8
9
|
import { copyFileSync, existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
9
10
|
import { tmpdir } from "node:os";
|
|
@@ -16,7 +17,7 @@ const MACOS_CHROMIUM_KEY_ITERATIONS = 1003;
|
|
|
16
17
|
|
|
17
18
|
function spawnCapture(command, args, options = {}) {
|
|
18
19
|
return new Promise((resolve) => {
|
|
19
|
-
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
20
|
+
const child = spawn(command, args, { env: sweetCookieSafeStoragePasswordScrubbedEnv(), stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
|
|
20
21
|
let stdout = "";
|
|
21
22
|
let stderr = "";
|
|
22
23
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
@@ -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.5",
|
|
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:@earendil-works/pi-ai --external:typebox --outfile=/tmp/pi-oracle-extension-check.js",
|
|
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:@earendil-works/pi-ai --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"
|
|
@@ -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,59 @@
|
|
|
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
|
+
release: {
|
|
22
|
+
description: "Doctor-first packed-install macOS/Ubuntu/Windows release proof.",
|
|
23
|
+
commands: ["npm run smoke:platform:all"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
requiredCrabbox: {
|
|
27
|
+
source: "https://github.com/openclaw/crabbox",
|
|
28
|
+
minVersion: "0.24.0",
|
|
29
|
+
},
|
|
30
|
+
ubuntuContainerImage: "pi-oracle-platform-smoke:node24",
|
|
31
|
+
ubuntuContainerBaseImage: "cimg/node:24.16",
|
|
32
|
+
windowsParallels: {
|
|
33
|
+
sourceVm: "pi-extension-windows-template",
|
|
34
|
+
snapshot: "crabbox-ready",
|
|
35
|
+
},
|
|
36
|
+
nodeValidationMajor: 24,
|
|
37
|
+
realSmoke: {
|
|
38
|
+
defaultProvider: "zai",
|
|
39
|
+
defaultModel: "glm-5.1",
|
|
40
|
+
authEnvByProvider: {
|
|
41
|
+
zai: ["ZAI_API_KEY"],
|
|
42
|
+
openai: ["OPENAI_API_KEY"],
|
|
43
|
+
anthropic: ["ANTHROPIC_API_KEY", "ANTHROPIC_OAUTH_TOKEN"],
|
|
44
|
+
google: ["GEMINI_API_KEY"],
|
|
45
|
+
xai: ["XAI_API_KEY"],
|
|
46
|
+
groq: ["GROQ_API_KEY"],
|
|
47
|
+
deepseek: ["DEEPSEEK_API_KEY"],
|
|
48
|
+
cerebras: ["CEREBRAS_API_KEY"],
|
|
49
|
+
fireworks: ["FIREWORKS_API_KEY"],
|
|
50
|
+
together: ["TOGETHER_API_KEY"],
|
|
51
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
52
|
+
ai_gateway: ["AI_GATEWAY_API_KEY"],
|
|
53
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
54
|
+
minimax: ["MINIMAX_API_KEY"],
|
|
55
|
+
moonshot: ["MOONSHOT_API_KEY"],
|
|
56
|
+
kimi: ["KIMI_API_KEY"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|