pi-oracle 0.7.6 → 0.7.8
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 +30 -0
- package/README.md +7 -5
- package/docs/ORACLE_DESIGN.md +17 -11
- package/docs/ORACLE_ISOLATED_PI_VALIDATION.md +22 -21
- package/docs/platform-smoke.md +5 -5
- package/extensions/oracle/index.ts +84 -4
- package/extensions/oracle/lib/auth.ts +4 -4
- package/extensions/oracle/lib/commands.ts +48 -22
- package/extensions/oracle/lib/config.ts +63 -9
- package/extensions/oracle/lib/poller.ts +20 -5
- package/extensions/oracle/lib/runtime.ts +8 -0
- package/extensions/oracle/lib/tools.ts +18 -5
- package/extensions/oracle/shared/browser-profile-helpers.d.mts +15 -0
- package/extensions/oracle/shared/browser-profile-helpers.mjs +37 -13
- package/extensions/oracle/shared/job-observability-helpers.d.mts +3 -1
- package/extensions/oracle/shared/job-observability-helpers.mjs +14 -5
- package/extensions/oracle/worker/chatgpt-flow-helpers.d.mts +9 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.mjs +29 -2
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +1 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +52 -13
- package/extensions/oracle/worker/run-job.mjs +179 -53
- package/package.json +3 -6
- package/prompts/oracle-followup.md +2 -2
- package/prompts/oracle.md +13 -5
- package/scripts/oracle-real-smoke.mjs +10 -5
- package/scripts/platform-smoke/platform-build-windows.ps1 +2 -2
- package/scripts/platform-smoke/targets.mjs +2 -2
|
@@ -29,15 +29,19 @@ 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
|
|
34
|
-
const COMPACT_INTELLIGENCE_OPENER_PATTERN = /^(?:Instant|Medium|High|Pro)$/i;
|
|
32
|
+
const COMPACT_INTELLIGENCE_MENU_PATTERN = /(?:Intelligence.*Instant.*Medium.*High.*Pro|^(?:Instant|Medium|High|Extra High|Pro Standard|Pro Extended)$)/i;
|
|
33
|
+
const COMPACT_INTELLIGENCE_CONTROL_PATTERN = /^(?:Instant(?:\s+5s)?|Medium(?:\s+5\s*[–-]\s*30s)?|High(?:\s+15\s*[–-]\s*60s)?|Extra High|Pro\s+5\+\s*min|Pro Standard|Pro Extended)$/i;
|
|
34
|
+
const COMPACT_INTELLIGENCE_OPENER_PATTERN = /^(?:Instant|Medium|High|Extra High|Pro|Pro Standard|Pro Extended)$/i;
|
|
35
35
|
const BARE_EFFORT_PATTERN = /^(light|standard|extended|heavy)(?:, click to remove)?$/i;
|
|
36
36
|
const INSTANT_CHIP_PATTERN = /^instant(?:, click to remove)?$/i;
|
|
37
37
|
const THINKING_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?thinking(?:, click to remove)?$/i;
|
|
38
38
|
const PRO_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?pro(?:, click to remove)?$/i;
|
|
39
39
|
const MODEL_FAMILY_CONTROL_KINDS = new Set(["button", "radio", "menuitemradio"]);
|
|
40
40
|
const COMPACT_INTELLIGENCE_CONTROL_KINDS = new Set(["menuitemradio"]);
|
|
41
|
+
const CHATGPT_RESPONSE_CHROME_LINE_PATTERNS = Object.freeze([
|
|
42
|
+
/^Stopped thinking$/i,
|
|
43
|
+
/^Do you like this personality\?$/i,
|
|
44
|
+
]);
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
47
|
* @param {string | undefined} url
|
|
@@ -90,6 +94,18 @@ export function buildAllowedChatGptOrigins(chatUrl, authUrl) {
|
|
|
90
94
|
]);
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {string | undefined} value
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
export function stripChatGptResponseChrome(value) {
|
|
102
|
+
return String(value || "")
|
|
103
|
+
.split("\n")
|
|
104
|
+
.filter((line) => !CHATGPT_RESPONSE_CHROME_LINE_PATTERNS.some((pattern) => pattern.test(line.trim())))
|
|
105
|
+
.join("\n")
|
|
106
|
+
.trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
/**
|
|
94
110
|
* @param {string | undefined} label
|
|
95
111
|
* @param {OracleUiModelFamily} family
|
|
@@ -144,6 +160,14 @@ function parseComposerChipSelection(label) {
|
|
|
144
160
|
};
|
|
145
161
|
}
|
|
146
162
|
|
|
163
|
+
const proPrefixedEffortMatch = normalized.match(/^pro\s+(standard|extended)$/i);
|
|
164
|
+
if (proPrefixedEffortMatch) {
|
|
165
|
+
return {
|
|
166
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("pro"),
|
|
167
|
+
effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ (proPrefixedEffortMatch[1].toLowerCase()),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
147
171
|
const proMatch = normalized.match(PRO_CHIP_PATTERN);
|
|
148
172
|
if (proMatch) {
|
|
149
173
|
return {
|
|
@@ -180,7 +204,22 @@ function parseCompactIntelligenceSelection(label) {
|
|
|
180
204
|
compactTier: "high",
|
|
181
205
|
};
|
|
182
206
|
}
|
|
183
|
-
if (/^
|
|
207
|
+
if (/^Extra High$/i.test(normalized)) {
|
|
208
|
+
return {
|
|
209
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("thinking"),
|
|
210
|
+
effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ ("heavy"),
|
|
211
|
+
compactTier: "extra-high",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const proEffortMatch = normalized.match(/^Pro\s+(Standard|Extended)$/i);
|
|
215
|
+
if (proEffortMatch) {
|
|
216
|
+
return {
|
|
217
|
+
modelFamily: /** @type {OracleUiModelFamily} */ ("pro"),
|
|
218
|
+
effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ (proEffortMatch[1].toLowerCase()),
|
|
219
|
+
compactTier: "pro",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (/^Pro\s+5\+\s*min$/i.test(normalized)) {
|
|
184
223
|
return {
|
|
185
224
|
modelFamily: /** @type {OracleUiModelFamily} */ ("pro"),
|
|
186
225
|
compactTier: "pro",
|
|
@@ -198,7 +237,7 @@ function hasRemovableComposerModelChip(entries) {
|
|
|
198
237
|
|
|
199
238
|
function hasCompactIntelligenceMenuContext(entries) {
|
|
200
239
|
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 &&
|
|
240
|
+
|| entries.some((entry) => !entry.disabled && entry.kind === "menuitemradio" && checkedState(entry) === true && parseCompactIntelligenceSelection(entry.label));
|
|
202
241
|
}
|
|
203
242
|
|
|
204
243
|
function hasLegacyEffortCombobox(entries) {
|
|
@@ -211,7 +250,6 @@ function hasLegacyEffortCombobox(entries) {
|
|
|
211
250
|
|
|
212
251
|
function compactSelectionFromEntry(entry, _entries, _options = {}) {
|
|
213
252
|
if (entry.disabled || !COMPACT_INTELLIGENCE_CONTROL_KINDS.has(entry.kind || "")) return undefined;
|
|
214
|
-
if (!/\d/.test(String(entry.label || ""))) return undefined;
|
|
215
253
|
return parseCompactIntelligenceSelection(entry.label);
|
|
216
254
|
}
|
|
217
255
|
|
|
@@ -225,14 +263,16 @@ function compactSelectionMatchesRequested(selection, compactSelection) {
|
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
if (selection.modelFamily === "pro") {
|
|
228
|
-
|
|
229
|
-
|
|
266
|
+
if (compactSelection.compactTier !== "pro") return false;
|
|
267
|
+
if (!compactSelection.effort) return true;
|
|
268
|
+
return compactSelection.effort === (selection.effort || "standard");
|
|
230
269
|
}
|
|
231
270
|
|
|
232
271
|
if (selection.modelFamily === "thinking") {
|
|
233
272
|
const requestedEffort = selection.effort || "standard";
|
|
234
273
|
if (compactSelection.compactTier === "medium") return requestedEffort === "light" || requestedEffort === "standard";
|
|
235
|
-
if (compactSelection.compactTier === "high") return requestedEffort === "extended"
|
|
274
|
+
if (compactSelection.compactTier === "high") return requestedEffort === "extended";
|
|
275
|
+
if (compactSelection.compactTier === "extra-high") return requestedEffort === "heavy";
|
|
236
276
|
}
|
|
237
277
|
|
|
238
278
|
return false;
|
|
@@ -353,7 +393,7 @@ export function effortSelectionVisible(snapshot, effortLabel) {
|
|
|
353
393
|
if (compactSelection?.modelFamily === "thinking") {
|
|
354
394
|
return compactSelectionMatchesRequested({ modelFamily: "thinking", effort: /** @type {import("./chatgpt-ui-helpers.d.mts").OracleUiEffort} */ (normalizedEffort), autoSwitchToThinking: false }, compactSelection);
|
|
355
395
|
}
|
|
356
|
-
if (compactSelection?.modelFamily === "pro") return
|
|
396
|
+
if (compactSelection?.modelFamily === "pro") return !compactSelection.effort || compactSelection.effort === normalizedEffort;
|
|
357
397
|
if (entry.kind === "combobox" && normalizeText(entry.value).toLowerCase() === normalizedEffort) return true;
|
|
358
398
|
const chipSelection = entry.kind === "button" ? parseComposerChipSelection(entry.label) : undefined;
|
|
359
399
|
if (chipSelection?.effort === normalizedEffort) return true;
|
|
@@ -395,17 +435,16 @@ export function snapshotHasModelConfigurationUi(snapshot) {
|
|
|
395
435
|
),
|
|
396
436
|
);
|
|
397
437
|
const visibleCompactControls = entries.filter(
|
|
398
|
-
(entry) => !entry.disabled && entry.kind === "menuitemradio" &&
|
|
438
|
+
(entry) => !entry.disabled && entry.kind === "menuitemradio" && parseCompactIntelligenceSelection(entry.label),
|
|
399
439
|
);
|
|
400
440
|
const hasCompactIntelligenceMenu = entries.some(
|
|
401
441
|
(entry) => !entry.disabled && entry.kind === "menu" && COMPACT_INTELLIGENCE_MENU_PATTERN.test(normalizeText(entry.label)),
|
|
402
442
|
);
|
|
403
|
-
const hasCloseButton = entries.some((entry) => entry.kind === "button" && entry.label === "Close" && !entry.disabled);
|
|
404
443
|
const hasIntelligenceHeading = entries.some((entry) => entry.kind === "heading" && normalizeText(entry.label) === "Intelligence" && !entry.disabled);
|
|
405
444
|
const hasEffortCombobox = entries.some(
|
|
406
445
|
(entry) => entry.kind === "combobox" && EFFORT_LABELS.has(entry.value || "") && !entry.disabled,
|
|
407
446
|
);
|
|
408
|
-
return visibleFamilies.size >= 2 || visibleRadioFamilies.size >= 2 || visibleCompactControls.length >= 2 || hasCompactIntelligenceMenu ||
|
|
447
|
+
return visibleFamilies.size >= 2 || visibleRadioFamilies.size >= 2 || visibleCompactControls.length >= 2 || hasCompactIntelligenceMenu || hasIntelligenceHeading || hasEffortCombobox;
|
|
409
448
|
}
|
|
410
449
|
|
|
411
450
|
/**
|
|
@@ -30,12 +30,14 @@ import {
|
|
|
30
30
|
effortSelectionVisible,
|
|
31
31
|
snapshotCanSafelySkipModelConfiguration,
|
|
32
32
|
snapshotHasModelConfigurationUi,
|
|
33
|
+
snapshotHasModelOpener,
|
|
33
34
|
snapshotHasUsableComposerControls,
|
|
34
35
|
snapshotStronglyMatchesRequestedModel,
|
|
35
36
|
snapshotWeaklyMatchesRequestedModel,
|
|
36
37
|
autoSwitchToThinkingSelectionVisible,
|
|
38
|
+
stripChatGptResponseChrome,
|
|
37
39
|
} from "./chatgpt-ui-helpers.mjs";
|
|
38
|
-
import { assistantSnapshotSlice, nextStableValueState, resolveStableConversationUrlCandidate, stripUrlQueryAndHash } from "./chatgpt-flow-helpers.mjs";
|
|
40
|
+
import { assistantSnapshotSlice, nextStableValueState, providerSendAccepted, resolveStableConversationUrlCandidate, stripUrlQueryAndHash } from "./chatgpt-flow-helpers.mjs";
|
|
39
41
|
import { assertNotKnownBrowserUserDataPath, scrubSweetCookieSafeStoragePasswordEnv, sweetCookieSafeStoragePasswordScrubbedEnv } from "../shared/browser-profile-helpers.mjs";
|
|
40
42
|
import { createLease, listLeaseMetadata, readLeaseMetadata, releaseLease, withLock } from "./state-locks.mjs";
|
|
41
43
|
|
|
@@ -86,6 +88,7 @@ const AGENT_BROWSER_BIN = [process.env.AGENT_BROWSER_PATH, "/opt/homebrew/bin/ag
|
|
|
86
88
|
const CP_BIN = process.env.PI_ORACLE_CP_PATH?.trim() || "cp";
|
|
87
89
|
scrubSweetCookieSafeStoragePasswordEnv();
|
|
88
90
|
|
|
91
|
+
let cpSupportsApfsCloneFlag;
|
|
89
92
|
let currentJob;
|
|
90
93
|
let browserStarted = false;
|
|
91
94
|
let cleaningUpBrowser = false;
|
|
@@ -277,6 +280,14 @@ function spawnCommand(command, args, options = {}) {
|
|
|
277
280
|
});
|
|
278
281
|
}
|
|
279
282
|
|
|
283
|
+
async function cpSupportsApfsClone() {
|
|
284
|
+
if (process.platform !== "darwin") return false;
|
|
285
|
+
if (cpSupportsApfsCloneFlag !== undefined) return cpSupportsApfsCloneFlag;
|
|
286
|
+
const probe = await spawnCommand(CP_BIN, ["-c"], { allowFailure: true, timeoutMs: 5_000 });
|
|
287
|
+
cpSupportsApfsCloneFlag = !/invalid option\s+--\s+['"]?c/i.test(`${probe.stderr}\n${probe.stdout}`);
|
|
288
|
+
return cpSupportsApfsCloneFlag;
|
|
289
|
+
}
|
|
290
|
+
|
|
280
291
|
function parseConversationId(chatUrl) {
|
|
281
292
|
if (!chatUrl) return undefined;
|
|
282
293
|
try {
|
|
@@ -321,7 +332,7 @@ async function cloneSeedProfileToRuntime(job) {
|
|
|
321
332
|
await withLock(ORACLE_STATE_DIR, "auth", "global", { jobId: job.id, processPid: process.pid, action: "cloneSeedProfile" }, async () => {
|
|
322
333
|
await rm(job.runtimeProfileDir, { recursive: true, force: true }).catch(() => undefined);
|
|
323
334
|
await ensurePrivateDir(dirname(job.runtimeProfileDir));
|
|
324
|
-
if (job.config.browser.cloneStrategy === "apfs-clone" &&
|
|
335
|
+
if (job.config.browser.cloneStrategy === "apfs-clone" && await cpSupportsApfsClone()) {
|
|
325
336
|
try {
|
|
326
337
|
await spawnCommand(CP_BIN, ["-cR", seedDir, job.runtimeProfileDir], { timeoutMs: PROFILE_CLONE_TIMEOUT_MS });
|
|
327
338
|
} catch (error) {
|
|
@@ -774,8 +785,8 @@ function snapshotHasCompactIntelligenceMenuControls(snapshot) {
|
|
|
774
785
|
return Boolean(findEntry(snapshot, (candidate) => {
|
|
775
786
|
if (candidate.disabled) return false;
|
|
776
787
|
const label = normalizeSnapshotLabel(candidate.label);
|
|
777
|
-
return (candidate.kind === "menu" && /Intelligence.*Instant.*Medium.*High.*Pro/i.test(label))
|
|
778
|
-
|| (candidate.kind === "menuitemradio" && /^(?:Instant
|
|
788
|
+
return (candidate.kind === "menu" && /(?:Intelligence.*Instant.*Medium.*High.*Pro|^(?:Instant|Medium|High|Extra High|Pro Extended)$)/i.test(label))
|
|
789
|
+
|| (candidate.kind === "menuitemradio" && /^(?:Instant(?:\s+5s)?|Medium(?:\s+5\s*[–-]\s*30s)?|High(?:\s+15\s*[–-]\s*60s)?|Extra High|Pro\s+5\+\s*min|Pro Standard|Pro Extended)$/i.test(label));
|
|
779
790
|
}));
|
|
780
791
|
}
|
|
781
792
|
|
|
@@ -783,9 +794,10 @@ function matchesRequestedModelControl(candidate, selection, options = {}) {
|
|
|
783
794
|
if (!["button", "radio", "menuitemradio"].includes(candidate.kind || "") || typeof candidate.label !== "string" || candidate.disabled) return false;
|
|
784
795
|
if (candidate.kind === "button") {
|
|
785
796
|
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;
|
|
797
|
+
if (options.ignoreCompactTierButtons && /^(?:Instant|Medium|High|Extra High|Pro|Pro Extended)$/i.test(candidate.label)) return false;
|
|
798
|
+
if (options.ignoreCompactOnlyButtons && /^(?:Medium|High|Extra High)$/i.test(candidate.label)) return false;
|
|
788
799
|
}
|
|
800
|
+
if (selection.modelFamily === "pro" && /^Pro(?:\s+Extended)?$/i.test(candidate.label)) return true;
|
|
789
801
|
return matchesRequestedModelControlLabel(candidate.label, selection);
|
|
790
802
|
}
|
|
791
803
|
|
|
@@ -859,7 +871,41 @@ async function maybeClickLabeledEntry(job, label, options = {}) {
|
|
|
859
871
|
}
|
|
860
872
|
|
|
861
873
|
async function openEffortDropdown(job) {
|
|
862
|
-
|
|
874
|
+
let snapshot = await snapshotText(job);
|
|
875
|
+
if (job.selection?.modelFamily === "pro") {
|
|
876
|
+
let proEffortEntry = findEntry(
|
|
877
|
+
snapshot,
|
|
878
|
+
(candidate) => candidate.kind === "menuitem" && candidate.label === "Pro effort options" && !candidate.disabled,
|
|
879
|
+
);
|
|
880
|
+
if (!proEffortEntry) {
|
|
881
|
+
const opener = findEntry(snapshot, matchesModelConfigurationOpener);
|
|
882
|
+
if (opener) {
|
|
883
|
+
await clickRef(job, opener.ref);
|
|
884
|
+
await agentBrowser(job, "wait", "500");
|
|
885
|
+
snapshot = await snapshotText(job);
|
|
886
|
+
proEffortEntry = findEntry(
|
|
887
|
+
snapshot,
|
|
888
|
+
(candidate) => candidate.kind === "menuitem" && candidate.label === "Pro effort options" && !candidate.disabled,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (proEffortEntry) {
|
|
893
|
+
try {
|
|
894
|
+
await clickRef(job, proEffortEntry.ref);
|
|
895
|
+
return true;
|
|
896
|
+
} catch {
|
|
897
|
+
// Fall through to DOM click. ChatGPT's tiny trailing Pro effort icon can
|
|
898
|
+
// be covered at the accessibility click point by the parent Pro row.
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const clicked = await evalPage(job, toJsonScript(`
|
|
902
|
+
const el = document.querySelector('[aria-label="Pro effort options"], [data-composer-intelligence-pro-effort-action]');
|
|
903
|
+
if (!el) return false;
|
|
904
|
+
el.click();
|
|
905
|
+
return true;
|
|
906
|
+
`));
|
|
907
|
+
if (clicked) return true;
|
|
908
|
+
}
|
|
863
909
|
const effortLabels = new Set(["Light", "Standard", "Extended", "Heavy"]);
|
|
864
910
|
const entry = findEntry(
|
|
865
911
|
snapshot,
|
|
@@ -1157,46 +1203,128 @@ async function waitForSendReady(job) {
|
|
|
1157
1203
|
throw new Error(`Timed out waiting for ${labelsForJob(job).send} to become enabled`);
|
|
1158
1204
|
}
|
|
1159
1205
|
|
|
1160
|
-
async function
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (
|
|
1206
|
+
async function activateSendButton(job) {
|
|
1207
|
+
const result = await evalPage(job, toJsonScript(`
|
|
1208
|
+
const labels = ${JSON.stringify(labelsForJob(job))};
|
|
1209
|
+
const buttons = Array.from(document.querySelectorAll('button'));
|
|
1210
|
+
const button = buttons.find((candidate) => {
|
|
1211
|
+
const label = (candidate.getAttribute('aria-label') || candidate.textContent || '').trim();
|
|
1212
|
+
return label === labels.send;
|
|
1213
|
+
});
|
|
1214
|
+
if (!button) return { ok: false, reason: 'send button not found' };
|
|
1215
|
+
if (button.disabled || button.getAttribute('aria-disabled') === 'true') return { ok: false, reason: 'send button disabled' };
|
|
1216
|
+
button.click();
|
|
1217
|
+
return { ok: true };
|
|
1218
|
+
`));
|
|
1219
|
+
return result;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
async function sendAcceptanceState(job, baselineAssistantCount) {
|
|
1223
|
+
const [urlResult, snapshot, messages] = await Promise.all([
|
|
1224
|
+
currentUrl(job).then((url) => ({ url, ok: true })).catch(() => ({ url: "", ok: false })),
|
|
1225
|
+
snapshotText(job).catch(() => ""),
|
|
1226
|
+
assistantMessages(job).catch(() => []),
|
|
1227
|
+
]);
|
|
1228
|
+
return {
|
|
1229
|
+
url: urlResult.url,
|
|
1230
|
+
urlKnown: urlResult.ok,
|
|
1231
|
+
assistantCount: Math.max(baselineAssistantCount, messages.length),
|
|
1232
|
+
stopStreaming: isGrokJob(job) ? snapshot.includes(GROK_LABELS.stop) : snapshot.includes("Stop streaming"),
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async function clickSend(job, baselineAssistantCount) {
|
|
1237
|
+
await waitForSendReady(job);
|
|
1238
|
+
const beforeSend = await sendAcceptanceState(job, baselineAssistantCount);
|
|
1239
|
+
const activation = await activateSendButton(job);
|
|
1240
|
+
if (!activation?.ok) throw new Error(`Could not activate ${labelsForJob(job).send}: ${activation?.reason || "DOM activation failed"}`);
|
|
1241
|
+
await log(`Activated ${labelsForJob(job).send}; waiting for provider acceptance evidence`);
|
|
1242
|
+
if (await waitForSendAccepted(job, beforeSend, { timeoutMs: 20_000 })) return;
|
|
1243
|
+
|
|
1244
|
+
await captureDiagnostics(job, "send-not-accepted");
|
|
1245
|
+
throw new Error(`${isGrokJob(job) ? "Grok" : "ChatGPT"} message did not leave the composer after activating ${labelsForJob(job).send}`);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
async function waitForSendAccepted(job, beforeSend, options = {}) {
|
|
1249
|
+
const timeoutAt = Date.now() + (options.timeoutMs || 15_000);
|
|
1250
|
+
while (Date.now() < timeoutAt) {
|
|
1251
|
+
await heartbeat();
|
|
1252
|
+
const afterSend = await sendAcceptanceState(job, beforeSend.assistantCount || 0);
|
|
1253
|
+
if (providerSendAccepted(beforeSend, afterSend)) return true;
|
|
1254
|
+
await sleep(500);
|
|
1170
1255
|
}
|
|
1171
|
-
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async function dismissProFeedbackModal(job, snapshot) {
|
|
1260
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
1261
|
+
const hasProFeedback = entries.some((entry) => entry.kind === "heading" && entry.label === "Pro feedback" && !entry.disabled);
|
|
1262
|
+
if (!hasProFeedback) return false;
|
|
1263
|
+
const close = entries.find((entry) => entry.kind === "button" && entry.label === CHATGPT_LABELS.close && !entry.disabled);
|
|
1264
|
+
if (close) {
|
|
1265
|
+
await clickRef(job, close.ref).catch(() => undefined);
|
|
1266
|
+
await agentBrowser(job, "wait", "500");
|
|
1267
|
+
if (!(await pageText(job).catch(() => "")).includes("Pro feedback")) return true;
|
|
1268
|
+
}
|
|
1269
|
+
await agentBrowser(job, "press", "Escape").catch(() => undefined);
|
|
1270
|
+
await agentBrowser(job, "wait", "500");
|
|
1271
|
+
if (!(await pageText(job).catch(() => "")).includes("Pro feedback")) return true;
|
|
1272
|
+
|
|
1273
|
+
const dismissed = await evalPage(job, toJsonScript(`
|
|
1274
|
+
const dialogText = document.body.innerText || '';
|
|
1275
|
+
if (!/Pro feedback/.test(dialogText)) return false;
|
|
1276
|
+
const button = Array.from(document.querySelectorAll('button'))
|
|
1277
|
+
.find((candidate) => (candidate.getAttribute('aria-label') || candidate.textContent || '').trim() === 'Close');
|
|
1278
|
+
if (!button) return false;
|
|
1279
|
+
button.click();
|
|
1280
|
+
return true;
|
|
1281
|
+
`));
|
|
1282
|
+
if (dismissed) await agentBrowser(job, "wait", "500");
|
|
1283
|
+
return Boolean(dismissed);
|
|
1172
1284
|
}
|
|
1173
1285
|
|
|
1174
1286
|
async function openModelConfiguration(job) {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1287
|
+
const timeoutAt = Date.now() + 15_000;
|
|
1288
|
+
let lastSnapshot = "";
|
|
1177
1289
|
|
|
1178
|
-
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
if (
|
|
1182
|
-
await
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1290
|
+
while (Date.now() < timeoutAt) {
|
|
1291
|
+
const initialSnapshot = await snapshotText(job);
|
|
1292
|
+
lastSnapshot = initialSnapshot;
|
|
1293
|
+
if (snapshotHasModelConfigurationUi(initialSnapshot)) return initialSnapshot;
|
|
1294
|
+
if (await dismissProFeedbackModal(job, initialSnapshot)) continue;
|
|
1295
|
+
|
|
1296
|
+
for (const predicate of [matchesModelConfigurationOpener]) {
|
|
1297
|
+
const snapshot = await snapshotText(job);
|
|
1298
|
+
lastSnapshot = snapshot;
|
|
1299
|
+
const entry = findEntry(snapshot, predicate);
|
|
1300
|
+
if (!entry) continue;
|
|
1301
|
+
await clickRef(job, entry.ref);
|
|
1302
|
+
await agentBrowser(job, "wait", "800");
|
|
1303
|
+
const after = await snapshotText(job);
|
|
1304
|
+
lastSnapshot = after;
|
|
1305
|
+
if (snapshotHasModelConfigurationUi(after)) return after;
|
|
1306
|
+
if (canUseOpenModelMenuForSelection(after, job.selection)) return after;
|
|
1307
|
+
|
|
1308
|
+
const configureEntry = findEntry(
|
|
1309
|
+
after,
|
|
1310
|
+
(candidate) => candidate.kind === "menuitem" && candidate.label === CHATGPT_LABELS.configure && !candidate.disabled,
|
|
1311
|
+
);
|
|
1187
1312
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1313
|
+
if (configureEntry) {
|
|
1314
|
+
await clickRef(job, configureEntry.ref);
|
|
1315
|
+
await agentBrowser(job, "wait", "1200");
|
|
1316
|
+
const postConfigure = await snapshotText(job);
|
|
1317
|
+
lastSnapshot = postConfigure;
|
|
1318
|
+
if (snapshotHasModelConfigurationUi(postConfigure)) return postConfigure;
|
|
1319
|
+
if (canUseOpenModelMenuForSelection(postConfigure, job.selection)) return postConfigure;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1192
1322
|
|
|
1193
|
-
if (
|
|
1194
|
-
await
|
|
1195
|
-
|
|
1196
|
-
const postConfigure = await snapshotText(job);
|
|
1197
|
-
if (snapshotHasModelConfigurationUi(postConfigure)) return postConfigure;
|
|
1198
|
-
if (canUseOpenModelMenuForSelection(postConfigure, job.selection)) return postConfigure;
|
|
1323
|
+
if (composerControlsVisible(lastSnapshot, job) && !snapshotHasModelOpener(lastSnapshot)) {
|
|
1324
|
+
await agentBrowser(job, "wait", "1000");
|
|
1325
|
+
continue;
|
|
1199
1326
|
}
|
|
1327
|
+
await agentBrowser(job, "wait", "500");
|
|
1200
1328
|
}
|
|
1201
1329
|
|
|
1202
1330
|
throw new Error("Could not open model configuration UI");
|
|
@@ -1303,7 +1431,11 @@ async function configureModel(job) {
|
|
|
1303
1431
|
throw new Error(`Could not open effort dropdown for requested effort: ${effortLabel}`);
|
|
1304
1432
|
}
|
|
1305
1433
|
await agentBrowser(job, "wait", "300");
|
|
1306
|
-
await
|
|
1434
|
+
if (job.selection.modelFamily === "pro" && await maybeClickLabeledEntry(job, `Pro ${effortLabel}`, { kind: "menuitemradio" })) {
|
|
1435
|
+
// Current ChatGPT exposes Pro effort choices as nested menu radio items.
|
|
1436
|
+
} else {
|
|
1437
|
+
await clickLabeledEntry(job, effortLabel, { kind: "option" });
|
|
1438
|
+
}
|
|
1307
1439
|
await agentBrowser(job, "wait", "400");
|
|
1308
1440
|
const effortSnapshot = await snapshotText(job);
|
|
1309
1441
|
verificationSnapshot = effortSnapshot;
|
|
@@ -2000,15 +2132,8 @@ async function downloadArtifacts(job, responseIndex, responseText = "") {
|
|
|
2000
2132
|
}
|
|
2001
2133
|
}
|
|
2002
2134
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const missedArtifactLabels = suspiciousLabels.filter((label) => !capturedArtifactLabels.has(label) && !capturedArtifactKeys.has(String(label).replace(/\s+/g, "")));
|
|
2006
|
-
if (missedArtifactLabels.length > 0) {
|
|
2007
|
-
await log(`Marking missed artifact signals as unconfirmed: ${missedArtifactLabels.join(", ")}`);
|
|
2008
|
-
for (const label of missedArtifactLabels) {
|
|
2009
|
-
artifacts.push({ displayName: label, unconfirmed: true, error: "Response-local artifact signal was present, but no downloadable artifact was captured." });
|
|
2010
|
-
}
|
|
2011
|
-
await flushArtifactsState(artifacts);
|
|
2135
|
+
if (suspiciousLabels.length > 0) {
|
|
2136
|
+
await log(`Ignoring plain-text artifact-like labels without downloadable controls: ${suspiciousLabels.join(", ")}`);
|
|
2012
2137
|
}
|
|
2013
2138
|
|
|
2014
2139
|
return artifacts;
|
|
@@ -2079,8 +2204,8 @@ async function run() {
|
|
|
2079
2204
|
await setComposerText(currentJob, await readFile(currentJob.promptPath, "utf8"));
|
|
2080
2205
|
const baselineAssistantCount = (await assistantMessages(currentJob)).length;
|
|
2081
2206
|
await log(`Assistant response count before send: ${baselineAssistantCount}`);
|
|
2082
|
-
await clickSend(currentJob);
|
|
2083
|
-
await log(`
|
|
2207
|
+
await clickSend(currentJob, baselineAssistantCount);
|
|
2208
|
+
await log(`Send accepted; waiting ${POST_SEND_SETTLE_MS}ms after send to avoid streaming interruption`);
|
|
2084
2209
|
await sleep(POST_SEND_SETTLE_MS);
|
|
2085
2210
|
|
|
2086
2211
|
const observedChatUrl = isGrokJob(currentJob)
|
|
@@ -2118,14 +2243,15 @@ async function run() {
|
|
|
2118
2243
|
message: "Extracting the completed response body.",
|
|
2119
2244
|
patch: { heartbeatAt: new Date().toISOString() },
|
|
2120
2245
|
}));
|
|
2121
|
-
|
|
2246
|
+
const responseText = isGrokJob(currentJob) ? completion.responseText.trim() : stripChatGptResponseChrome(completion.responseText);
|
|
2247
|
+
await secureWriteText(currentJob.responsePath, `${responseText}\n`);
|
|
2122
2248
|
currentJob = await mutateJob((job) => transitionOracleJobPhase(job, "downloading_artifacts", {
|
|
2123
2249
|
at: new Date().toISOString(),
|
|
2124
2250
|
source: "oracle:worker",
|
|
2125
2251
|
message: "Downloading any response artifacts.",
|
|
2126
2252
|
patch: { heartbeatAt: new Date().toISOString() },
|
|
2127
2253
|
}));
|
|
2128
|
-
const artifacts = await downloadArtifacts(currentJob, completion.responseIndex,
|
|
2254
|
+
const artifacts = await downloadArtifacts(currentJob, completion.responseIndex, responseText);
|
|
2129
2255
|
const artifactFailureCount = artifacts.filter((artifact) => artifact.unconfirmed || artifact.error).length;
|
|
2130
2256
|
const finalPhase = artifactFailureCount > 0 ? "complete_with_artifact_errors" : "complete";
|
|
2131
2257
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-oracle",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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",
|
|
@@ -41,9 +41,6 @@
|
|
|
41
41
|
"pi": {
|
|
42
42
|
"extensions": [
|
|
43
43
|
"./extensions/oracle/index.ts"
|
|
44
|
-
],
|
|
45
|
-
"prompts": [
|
|
46
|
-
"./prompts"
|
|
47
44
|
]
|
|
48
45
|
},
|
|
49
46
|
"scripts": {
|
|
@@ -83,8 +80,8 @@
|
|
|
83
80
|
"protobufjs": "7.6.1"
|
|
84
81
|
},
|
|
85
82
|
"devDependencies": {
|
|
86
|
-
"@earendil-works/pi-ai": "
|
|
87
|
-
"@earendil-works/pi-coding-agent": "
|
|
83
|
+
"@earendil-works/pi-ai": "0.79.1",
|
|
84
|
+
"@earendil-works/pi-coding-agent": "0.79.1",
|
|
88
85
|
"@types/node": "^22.19.19",
|
|
89
86
|
"esbuild": "^0.28.0",
|
|
90
87
|
"tsx": "^4.22.3",
|
|
@@ -10,7 +10,7 @@ Required workflow:
|
|
|
10
10
|
2. If the request does not include both a prior oracle job id and a follow-up request, stop and report: `Usage: /oracle-followup <job-id> <request>. Find the job id in the earlier oracle response or via /oracle-status.`
|
|
11
11
|
3. Same-thread follow-ups cannot switch providers. If the user asks to move a ChatGPT thread to Grok or a Grok thread to ChatGPT, stop and tell them to start a new `/oracle` job instead.
|
|
12
12
|
4. Call `oracle_preflight` immediately with the parsed `followUpJobId` so readiness checks use the prior job's provider.
|
|
13
|
-
5. If `oracle_preflight` reports `ready: false`, stop before any expensive prep. Do not read files, search the codebase,
|
|
13
|
+
5. If `oracle_preflight` reports `ready: false`, stop before any expensive prep. Do not read files, search the codebase, prepare archive inputs, or call `oracle_auth` automatically. Report the blocking issue plus the suggested next step.
|
|
14
14
|
6. Treat the parsed job id as `followUpJobId` for `oracle_submit`.
|
|
15
15
|
7. Understand whether the follow-up request is explicitly narrow or genuinely broad.
|
|
16
16
|
8. Gather enough repo context to choose archive inputs and write a strong oracle prompt. Bias toward context-rich submissions when they fit within the provider archive ceiling: 250 MB for ChatGPT, 200 MiB for Grok.
|
|
@@ -31,7 +31,7 @@ Oracle provider/model (`oracle_submit`):
|
|
|
31
31
|
|
|
32
32
|
Rules:
|
|
33
33
|
- Use `oracle_preflight` before any expensive `/oracle-followup` preparation so missing persisted-session or local auth/config blockers fail fast.
|
|
34
|
-
- If the immediately preceding oracle run for this follow-up failed because ChatGPT/Grok login is required, the worker said to rerun `/oracle-auth`, or stale auth clearly blocked execution,
|
|
34
|
+
- If the immediately preceding oracle run for this follow-up failed because ChatGPT/Grok login is required, the worker said to rerun `/oracle-auth`, or stale auth clearly blocked execution, stop and tell the user to run `/oracle-auth` (or `/oracle-auth grok` for Grok). Do not call `oracle_auth` automatically from `/oracle-followup`.
|
|
35
35
|
- This prompt exists so normal users can continue the same provider thread without manually constructing `followUpJobId` tool calls.
|
|
36
36
|
- Always include an archive. Do not submit without context files.
|
|
37
37
|
- By default, prefer context-rich archives up to the provider ceiling because more relevant context is usually better than less. The ceiling is 250 MB for ChatGPT and 200 MiB for Grok. For broad or unclear follow-up requests, include the whole repository by passing `.`. Default archive exclusions apply automatically, including common bulky outputs and obvious credentials/private data like `.env` files, key material, credential dotfiles, local database files, and nested `secrets/` directories anywhere in the repo.
|
package/prompts/oracle.md
CHANGED
|
@@ -5,20 +5,28 @@ You are preparing an /oracle job.
|
|
|
5
5
|
|
|
6
6
|
Do not answer the user's request directly yet.
|
|
7
7
|
|
|
8
|
+
Hard requirements:
|
|
9
|
+
- Do not plan instead of submitting. The point of `/oracle` is dispatch.
|
|
10
|
+
- Do not claim preflight, auth, archive prep, or submission happened unless the matching tool call actually happened in this turn.
|
|
11
|
+
- If the user explicitly says ChatGPT Instant or Instant, use provider `chatgpt` and preset `instant`; never switch that request to Grok.
|
|
12
|
+
- If a required tool call is unavailable or fails, stop and report that exact blocker instead of fabricating progress.
|
|
13
|
+
- After a successful or queued `oracle_submit`, your final answer must be only a terse dispatch summary with the job id and response path. Do not ask questions, offer to watch/poll/read, list next steps, or continue working.
|
|
14
|
+
|
|
8
15
|
Required workflow:
|
|
9
|
-
1. Call `oracle_preflight` immediately. If the user says to use Grok, pass `provider: "grok"` to `oracle_preflight`.
|
|
10
|
-
2. If `oracle_preflight` reports `ready: false`, stop before any expensive prep. Do not read files, search the codebase,
|
|
16
|
+
1. Call `oracle_preflight` immediately. If the user says to use Grok, pass `provider: "grok"` to `oracle_preflight`. If the user says to use ChatGPT, pass `provider: "chatgpt"`. If the user says to use ChatGPT Instant, pass `provider: "chatgpt"` and later call `oracle_submit` with `preset: "instant"`.
|
|
17
|
+
2. If `oracle_preflight` reports `ready: false`, stop before any expensive prep. Do not read files, search the codebase, prepare archive inputs, or call `oracle_auth` automatically. Report the blocking issue plus the suggested next step.
|
|
11
18
|
3. Understand the request and decide whether it is explicitly narrow or genuinely broad.
|
|
12
19
|
4. Gather enough repo context to choose archive inputs and write a strong oracle prompt. Bias toward context-rich submissions when they fit within the provider archive ceiling: 250 MB for ChatGPT, 200 MiB for Grok.
|
|
13
20
|
5. If the user scope is explicit and narrow, start from the directly relevant area but still include nearby files, tests, docs, configs, and adjacent modules when they may improve answer quality. Keep the archive tightly minimal only when the user explicitly asks for that, privacy/sensitivity requires it, or size pressure forces it.
|
|
14
21
|
6. If the request is broad, architectural, release-oriented, or otherwise repo-wide, gather broader context and usually archive `.`.
|
|
15
22
|
7. Choose archive inputs for the oracle job.
|
|
16
23
|
8. Craft a concise but complete oracle prompt for the selected web provider.
|
|
17
|
-
9. Call `oracle_submit` with the prompt and exact archive inputs.
|
|
18
|
-
10. Stop immediately after dispatching the oracle job.
|
|
24
|
+
9. Call `oracle_submit` with the prompt and exact archive inputs. Do not ask for confirmation before this submit step unless `oracle_preflight` or `oracle_submit` returns a blocker that requires user action.
|
|
25
|
+
10. Stop immediately after dispatching the oracle job. “Stop” means no follow-up questions, no offers to poll/watch/read, and no extra next-step list.
|
|
19
26
|
|
|
20
27
|
Oracle provider/model (`oracle_submit`):
|
|
21
28
|
- If the user says to use Grok (for example “Use the oracle to Grok about ...”), pass **`provider: "grok"`**. Grok currently supports only **`mode: "heavy"`**; omit `mode` unless the user explicitly says Heavy.
|
|
29
|
+
- If the user says ChatGPT, pass **`provider: "chatgpt"`**. Never route a ChatGPT request to Grok.
|
|
22
30
|
- Otherwise omit **`provider`** to use the configured default provider, or pass **`provider: "chatgpt"`** only when needed for clarity.
|
|
23
31
|
- To choose a specific ChatGPT model, pass **`preset`** with one of the allowed ids from the canonical preset registry.
|
|
24
32
|
- Matching human-readable preset labels and common hyphen/space variants are also accepted and normalized automatically, but prefer canonical ids when readily available.
|
|
@@ -28,7 +36,7 @@ Oracle provider/model (`oracle_submit`):
|
|
|
28
36
|
|
|
29
37
|
Rules:
|
|
30
38
|
- Use `oracle_preflight` before any expensive `/oracle` preparation so missing persisted-session or local auth/config blockers fail fast.
|
|
31
|
-
- If
|
|
39
|
+
- If ChatGPT/Grok login is required, the worker said to rerun `/oracle-auth`, or stale auth clearly blocked execution, stop and tell the user to run `/oracle-auth` (or `/oracle-auth grok` for Grok). Do not call `oracle_auth` automatically from `/oracle`.
|
|
32
40
|
- Always include an archive. Do not submit without context files.
|
|
33
41
|
- By default, prefer context-rich archives up to the provider ceiling because more relevant context is usually better than less. The ceiling is 250 MB for ChatGPT and 200 MiB for Grok. For broad or unclear requests, include the whole repository by passing `.`. Default archive exclusions apply automatically, including common bulky outputs and obvious credentials/private data like `.env` files, key material, credential dotfiles, local database files, and nested `secrets/` directories anywhere in the repo.
|
|
34
42
|
- Only limit file selection if the user explicitly requests a tight archive, if privacy/sensitivity requires it, or if the archive would otherwise exceed the size limit after exclusions/pruning.
|
|
@@ -22,7 +22,7 @@ function usage() {
|
|
|
22
22
|
console.log(`Usage: node scripts/oracle-real-smoke.mjs <doctor|run> [--mode packed|source]
|
|
23
23
|
|
|
24
24
|
Modes:
|
|
25
|
-
packed Release proof. npm pack -> clean pi project -> npm install tarball -> pi install -l -> run through installed package. Default.
|
|
25
|
+
packed Release proof. npm pack -> clean pi project -> npm install tarball -> pi install -l --approve -> run through installed package. Default.
|
|
26
26
|
source Inner-loop/debug only. Loads this checkout with pi --no-extensions -e extensions/oracle/index.ts.
|
|
27
27
|
|
|
28
28
|
Environment:
|
|
@@ -257,12 +257,12 @@ async function preparePackedProject({ runDir, provider, model, timeoutMs }) {
|
|
|
257
257
|
|
|
258
258
|
await mustRun(installDir, "npm-init", npm, ["init", "-y"], { cwd: piProject, env: process.env, timeoutMs: 60_000 });
|
|
259
259
|
await mustRun(installDir, "packed-node-install", npm, ["install", "--no-save", tarballPath], { cwd: piProject, env: process.env, timeoutMs: 120_000 });
|
|
260
|
-
await mustRun(installDir, "pi-install", piCommand(), ["install", "-l", `./node_modules/${PACKAGE_NAME}
|
|
260
|
+
await mustRun(installDir, "pi-install", piCommand(), ["install", "-l", `./node_modules/${PACKAGE_NAME}`, "--approve"], {
|
|
261
261
|
cwd: piProject,
|
|
262
262
|
env: { ...process.env, PI_OFFLINE: "1" },
|
|
263
263
|
timeoutMs: 120_000,
|
|
264
264
|
});
|
|
265
|
-
const piList = await mustRun(installDir, "pi-list", piCommand(), ["list"], {
|
|
265
|
+
const piList = await mustRun(installDir, "pi-list", piCommand(), ["list", "--approve"], {
|
|
266
266
|
cwd: piProject,
|
|
267
267
|
env: { ...process.env, PI_OFFLINE: "1" },
|
|
268
268
|
timeoutMs: 60_000,
|
|
@@ -342,6 +342,7 @@ console.log(JSON.stringify(result, null, 2));
|
|
|
342
342
|
async function runPiAgent({ prepared, agentDir, sessionDir, jobsDir, prompt, outDir, label, timeoutMs, stopAfterJobArchive = false }) {
|
|
343
343
|
mkdirSync(outDir, { recursive: true });
|
|
344
344
|
const args = [
|
|
345
|
+
"--approve",
|
|
345
346
|
"--print",
|
|
346
347
|
"--provider", prepared.provider,
|
|
347
348
|
"--model", prepared.model,
|
|
@@ -396,7 +397,9 @@ async function run(mode = "packed") {
|
|
|
396
397
|
const agent1 = join(runDir, "agent1");
|
|
397
398
|
const sessions1 = join(runDir, "sessions1");
|
|
398
399
|
const jobs1 = join(runDir, "jobs1");
|
|
399
|
-
|
|
400
|
+
const authSeed1 = join(agent1, "extensions", "oracle-auth-seed-profile");
|
|
401
|
+
mkdirSync(authSeed1, { recursive: true });
|
|
402
|
+
writeFileSync(join(authSeed1, ".oracle-seed-generation"), "real-smoke-fake-worker\n");
|
|
400
403
|
mkdirSync(sessions1, { recursive: true });
|
|
401
404
|
mkdirSync(jobs1, { recursive: true });
|
|
402
405
|
|
|
@@ -443,7 +446,9 @@ async function run(mode = "packed") {
|
|
|
443
446
|
const sessions2 = join(runDir, "sessions2");
|
|
444
447
|
const jobs2 = join(runDir, "jobs2");
|
|
445
448
|
const outside = join(tmpRoot, "outside");
|
|
446
|
-
|
|
449
|
+
const authSeed2 = join(agent2, "extensions", "oracle-auth-seed-profile");
|
|
450
|
+
mkdirSync(authSeed2, { recursive: true });
|
|
451
|
+
writeFileSync(join(authSeed2, ".oracle-seed-generation"), "real-smoke-fake-worker\n");
|
|
447
452
|
mkdirSync(sessions2, { recursive: true });
|
|
448
453
|
mkdirSync(jobs2, { recursive: true });
|
|
449
454
|
mkdirSync(outside, { recursive: true });
|