pi-oracle 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +7 -0
- package/docs/ORACLE_DESIGN.md +1 -1
- package/docs/ORACLE_ISOLATED_PI_VALIDATION.md +249 -0
- package/docs/ORACLE_RECOVERY_DRILL.md +5 -4
- package/extensions/oracle/index.ts +8 -1
- package/extensions/oracle/lib/commands.ts +11 -24
- package/extensions/oracle/lib/config.ts +5 -0
- package/extensions/oracle/lib/jobs.ts +117 -217
- package/extensions/oracle/lib/locks.ts +41 -209
- package/extensions/oracle/lib/poller.ts +14 -51
- package/extensions/oracle/lib/queue.ts +75 -112
- package/extensions/oracle/lib/runtime.ts +60 -14
- package/extensions/oracle/lib/tools.ts +70 -67
- package/extensions/oracle/shared/job-coordination-helpers.d.mts +84 -0
- package/extensions/oracle/shared/job-coordination-helpers.mjs +168 -0
- package/extensions/oracle/shared/job-lifecycle-helpers.d.mts +130 -0
- package/extensions/oracle/shared/job-lifecycle-helpers.mjs +377 -0
- package/extensions/oracle/shared/job-observability-helpers.d.mts +59 -0
- package/extensions/oracle/shared/job-observability-helpers.mjs +143 -0
- package/extensions/oracle/shared/process-helpers.d.mts +20 -0
- package/extensions/oracle/shared/process-helpers.mjs +128 -0
- package/extensions/oracle/shared/state-coordination-helpers.d.mts +43 -0
- package/extensions/oracle/shared/state-coordination-helpers.mjs +381 -0
- package/extensions/oracle/worker/artifact-heuristics.mjs +5 -0
- package/extensions/oracle/worker/auth-bootstrap.mjs +100 -139
- package/extensions/oracle/worker/auth-cookie-policy.mjs +5 -0
- package/extensions/oracle/worker/auth-flow-helpers.d.mts +41 -0
- package/extensions/oracle/worker/auth-flow-helpers.mjs +165 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.d.mts +13 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.mjs +85 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +33 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +292 -0
- package/extensions/oracle/worker/run-job.mjs +235 -380
- package/extensions/oracle/worker/state-locks.mjs +31 -216
- package/package.json +14 -5
- package/prompts/oracle.md +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type OracleUiModelFamily = "instant" | "thinking" | "pro";
|
|
2
|
+
export type OracleUiEffort = "light" | "standard" | "extended" | "heavy";
|
|
3
|
+
|
|
4
|
+
export interface OracleUiSelection {
|
|
5
|
+
modelFamily: OracleUiModelFamily;
|
|
6
|
+
effort?: OracleUiEffort;
|
|
7
|
+
autoSwitchToThinking?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare const CHATGPT_CANONICAL_APP_ORIGINS: readonly string[];
|
|
11
|
+
|
|
12
|
+
export declare function buildAllowedChatGptOrigins(chatUrl: string, authUrl?: string): string[];
|
|
13
|
+
export declare function matchesModelFamilyLabel(label: string | undefined, family: OracleUiModelFamily): boolean;
|
|
14
|
+
export declare function requestedEffortLabel(selection: OracleUiSelection): string | undefined;
|
|
15
|
+
export declare function effortSelectionVisible(snapshot: string, effortLabel: string | undefined): boolean;
|
|
16
|
+
export declare function thinkingChipVisible(snapshot: string): boolean;
|
|
17
|
+
export declare function snapshotHasModelConfigurationUi(snapshot: string): boolean;
|
|
18
|
+
export declare function autoSwitchToThinkingSelectionVisible(snapshot: string): boolean | undefined;
|
|
19
|
+
export declare function snapshotCanSafelySkipModelConfiguration(snapshot: string, selection: OracleUiSelection): boolean;
|
|
20
|
+
export declare function snapshotStronglyMatchesRequestedModel(snapshot: string, selection: OracleUiSelection): boolean;
|
|
21
|
+
export declare function snapshotWeaklyMatchesRequestedModel(snapshot: string, selection: OracleUiSelection): boolean;
|
|
22
|
+
export declare function buildAssistantCompletionSignature(args: {
|
|
23
|
+
responseText: string;
|
|
24
|
+
artifactLabels?: string[];
|
|
25
|
+
suspiciousArtifactLabels?: string[];
|
|
26
|
+
}): string | undefined;
|
|
27
|
+
export declare function deriveAssistantCompletionSignature(args: {
|
|
28
|
+
hasStopStreaming: boolean;
|
|
29
|
+
hasTargetCopyResponse: boolean;
|
|
30
|
+
responseText: string;
|
|
31
|
+
artifactLabels?: string[];
|
|
32
|
+
suspiciousArtifactLabels?: string[];
|
|
33
|
+
}): string | undefined;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Purpose: Provide pure ChatGPT UI interpretation helpers shared by oracle worker/auth flows.
|
|
2
|
+
// Responsibilities: Normalize allowed origins, interpret model-selection snapshots, and derive assistant-completion signatures.
|
|
3
|
+
// Scope: Pure snapshot/text heuristics only; browser I/O and retry loops stay in the worker/auth entrypoints.
|
|
4
|
+
// Usage: Imported by worker/auth runtime code and sanity tests to keep browser-driven logic behaviorally testable.
|
|
5
|
+
// Invariants/Assumptions: Snapshot text comes from agent-browser `snapshot -i`; helper outputs must stay deterministic and side-effect free.
|
|
6
|
+
|
|
7
|
+
import { parseSnapshotEntries } from "./artifact-heuristics.mjs";
|
|
8
|
+
|
|
9
|
+
/** @typedef {import("./chatgpt-ui-helpers.d.mts").OracleUiModelFamily} OracleUiModelFamily */
|
|
10
|
+
/** @typedef {import("./chatgpt-ui-helpers.d.mts").OracleUiSelection} OracleUiSelection */
|
|
11
|
+
/** @typedef {import("./artifact-heuristics.d.mts").SnapshotEntry} SnapshotEntry */
|
|
12
|
+
|
|
13
|
+
/** @typedef {{ responseText: string; artifactLabels?: string[]; suspiciousArtifactLabels?: string[] }} CompletionSignatureArgs */
|
|
14
|
+
/** @typedef {{ hasStopStreaming: boolean; hasTargetCopyResponse: boolean; responseText: string; artifactLabels?: string[]; suspiciousArtifactLabels?: string[] }} DerivedCompletionSignatureArgs */
|
|
15
|
+
|
|
16
|
+
export const CHATGPT_CANONICAL_APP_ORIGINS = Object.freeze([
|
|
17
|
+
"https://chatgpt.com",
|
|
18
|
+
"https://chat.openai.com",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/** @type {Record<OracleUiModelFamily, string>} */
|
|
22
|
+
const MODEL_FAMILY_PREFIX = {
|
|
23
|
+
instant: "Instant ",
|
|
24
|
+
thinking: "Thinking ",
|
|
25
|
+
pro: "Pro ",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const AUTO_SWITCH_LABEL = "Auto-switch to Thinking";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string | undefined} url
|
|
32
|
+
* @returns {string | undefined}
|
|
33
|
+
*/
|
|
34
|
+
function originFromUrl(url) {
|
|
35
|
+
if (typeof url !== "string" || !url.trim()) return undefined;
|
|
36
|
+
try {
|
|
37
|
+
return new URL(url).origin;
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Array<string | undefined>} values
|
|
45
|
+
* @returns {string[]}
|
|
46
|
+
*/
|
|
47
|
+
function uniqueStrings(values) {
|
|
48
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value))];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string | undefined} value
|
|
53
|
+
* @returns {string | undefined}
|
|
54
|
+
*/
|
|
55
|
+
function titleCase(value) {
|
|
56
|
+
return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string | undefined} value
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function normalizeText(value) {
|
|
64
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} chatUrl
|
|
69
|
+
* @param {string | undefined} authUrl
|
|
70
|
+
* @returns {string[]}
|
|
71
|
+
*/
|
|
72
|
+
export function buildAllowedChatGptOrigins(chatUrl, authUrl) {
|
|
73
|
+
return uniqueStrings([
|
|
74
|
+
...CHATGPT_CANONICAL_APP_ORIGINS,
|
|
75
|
+
originFromUrl(chatUrl),
|
|
76
|
+
originFromUrl(authUrl),
|
|
77
|
+
"https://auth.openai.com",
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string | undefined} label
|
|
83
|
+
* @param {OracleUiModelFamily} family
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
export function matchesModelFamilyLabel(label, family) {
|
|
87
|
+
const normalized = String(label || "");
|
|
88
|
+
const prefix = MODEL_FAMILY_PREFIX[family];
|
|
89
|
+
const exact = prefix.trim();
|
|
90
|
+
return normalized === exact || normalized.startsWith(prefix) || normalized.startsWith(`${exact},`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {OracleUiSelection} selection
|
|
95
|
+
* @returns {string | undefined}
|
|
96
|
+
*/
|
|
97
|
+
export function requestedEffortLabel(selection) {
|
|
98
|
+
return selection?.effort ? titleCase(selection.effort) : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {string} snapshot
|
|
103
|
+
* @param {string | undefined} effortLabel
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
export function effortSelectionVisible(snapshot, effortLabel) {
|
|
107
|
+
if (!effortLabel) return true;
|
|
108
|
+
/** @type {SnapshotEntry[]} */
|
|
109
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
110
|
+
return entries.some((entry) => {
|
|
111
|
+
if (entry.disabled) return false;
|
|
112
|
+
if (entry.kind === "combobox" && entry.value === effortLabel) return true;
|
|
113
|
+
if (entry.kind !== "button") return false;
|
|
114
|
+
const label = String(entry.label || "").toLowerCase();
|
|
115
|
+
const normalizedEffort = effortLabel.toLowerCase();
|
|
116
|
+
return (
|
|
117
|
+
label === normalizedEffort ||
|
|
118
|
+
label === `${normalizedEffort} thinking` ||
|
|
119
|
+
label === `${normalizedEffort}, click to remove` ||
|
|
120
|
+
label === `${normalizedEffort} thinking, click to remove`
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string} snapshot
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
export function thinkingChipVisible(snapshot) {
|
|
130
|
+
return /button "(?:Light|Standard|Extended|Heavy)(?: thinking)?(?:, click to remove)?"/i.test(snapshot);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} snapshot
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
export function snapshotHasModelConfigurationUi(snapshot) {
|
|
138
|
+
/** @type {SnapshotEntry[]} */
|
|
139
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
140
|
+
const visibleFamilies = new Set(
|
|
141
|
+
entries
|
|
142
|
+
.filter((entry) => entry.kind === "button" && typeof entry.label === "string")
|
|
143
|
+
.flatMap((entry) =>
|
|
144
|
+
/** @type {OracleUiModelFamily[]} */ (["instant", "thinking", "pro"])
|
|
145
|
+
.filter((family) => matchesModelFamilyLabel(entry.label, family)),
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
const hasCloseButton = entries.some((entry) => entry.kind === "button" && entry.label === "Close" && !entry.disabled);
|
|
149
|
+
const hasEffortCombobox = entries.some(
|
|
150
|
+
(entry) => entry.kind === "combobox" && ["Light", "Standard", "Extended", "Heavy"].includes(entry.value || "") && !entry.disabled,
|
|
151
|
+
);
|
|
152
|
+
return visibleFamilies.size >= 2 || hasCloseButton || hasEffortCombobox;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} snapshot
|
|
157
|
+
* @returns {boolean | undefined}
|
|
158
|
+
*/
|
|
159
|
+
export function autoSwitchToThinkingSelectionVisible(snapshot) {
|
|
160
|
+
/** @type {SnapshotEntry[]} */
|
|
161
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
162
|
+
let foundControl = false;
|
|
163
|
+
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
const controlText = normalizeText([entry.label, entry.value, entry.line].filter(Boolean).join(" "));
|
|
166
|
+
if (!controlText.toLowerCase().includes(AUTO_SWITCH_LABEL.toLowerCase())) continue;
|
|
167
|
+
foundControl = true;
|
|
168
|
+
|
|
169
|
+
if (/\b(?:checked|selected|enabled|on|active)\b/i.test(controlText)) return true;
|
|
170
|
+
if (/\b(?:unchecked|not checked|disabled|off)\b/i.test(controlText)) return false;
|
|
171
|
+
if (typeof entry.label === "string" && /click to remove/i.test(entry.label)) return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return foundControl ? false : undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {string} snapshot
|
|
179
|
+
* @param {OracleUiSelection} selection
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
*/
|
|
182
|
+
export function snapshotCanSafelySkipModelConfiguration(snapshot, selection) {
|
|
183
|
+
if (!snapshotStronglyMatchesRequestedModel(snapshot, selection)) return false;
|
|
184
|
+
|
|
185
|
+
if (selection.modelFamily === "thinking" || selection.modelFamily === "pro") {
|
|
186
|
+
const effortLabel = requestedEffortLabel(selection);
|
|
187
|
+
if (effortLabel && !effortSelectionVisible(snapshot, effortLabel)) return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (selection.modelFamily === "instant" && selection.autoSwitchToThinking) {
|
|
191
|
+
return autoSwitchToThinkingSelectionVisible(snapshot) === true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {string} snapshot
|
|
199
|
+
* @param {OracleUiSelection} selection
|
|
200
|
+
* @returns {boolean}
|
|
201
|
+
*/
|
|
202
|
+
export function snapshotStronglyMatchesRequestedModel(snapshot, selection) {
|
|
203
|
+
/** @type {SnapshotEntry[]} */
|
|
204
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
205
|
+
const familyMatched = entries.some((entry) => !entry.disabled && matchesModelFamilyLabel(entry.label, selection.modelFamily));
|
|
206
|
+
if (!familyMatched) return false;
|
|
207
|
+
|
|
208
|
+
const configurationUiVisible = snapshotHasModelConfigurationUi(snapshot);
|
|
209
|
+
const effortLabel = requestedEffortLabel(selection);
|
|
210
|
+
|
|
211
|
+
if (selection.modelFamily === "thinking" || selection.modelFamily === "pro") {
|
|
212
|
+
if (!effortLabel) return true;
|
|
213
|
+
if (effortSelectionVisible(snapshot, effortLabel)) return true;
|
|
214
|
+
return !configurationUiVisible;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (selection.modelFamily === "instant") {
|
|
218
|
+
const autoSwitchState = autoSwitchToThinkingSelectionVisible(snapshot);
|
|
219
|
+
if (selection.autoSwitchToThinking) {
|
|
220
|
+
return autoSwitchState === true || (!configurationUiVisible && autoSwitchState === undefined);
|
|
221
|
+
}
|
|
222
|
+
return autoSwitchState !== true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @param {string} snapshot
|
|
230
|
+
* @param {OracleUiSelection} selection
|
|
231
|
+
* @returns {boolean}
|
|
232
|
+
*/
|
|
233
|
+
export function snapshotWeaklyMatchesRequestedModel(snapshot, selection) {
|
|
234
|
+
/** @type {SnapshotEntry[]} */
|
|
235
|
+
const entries = parseSnapshotEntries(snapshot);
|
|
236
|
+
const familyMatched = entries.some((entry) => !entry.disabled && matchesModelFamilyLabel(entry.label, selection.modelFamily));
|
|
237
|
+
|
|
238
|
+
if (selection.modelFamily === "thinking") {
|
|
239
|
+
return familyMatched || effortSelectionVisible(snapshot, requestedEffortLabel(selection));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!familyMatched) return false;
|
|
243
|
+
|
|
244
|
+
if (selection.modelFamily === "pro") {
|
|
245
|
+
return !thinkingChipVisible(snapshot);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (selection.modelFamily === "instant") {
|
|
249
|
+
const autoSwitchState = autoSwitchToThinkingSelectionVisible(snapshot);
|
|
250
|
+
return selection.autoSwitchToThinking ? autoSwitchState !== false : autoSwitchState !== true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {CompletionSignatureArgs} args
|
|
258
|
+
* @returns {string | undefined}
|
|
259
|
+
*/
|
|
260
|
+
export function buildAssistantCompletionSignature({ responseText, artifactLabels = [], suspiciousArtifactLabels = [] }) {
|
|
261
|
+
const normalizedResponse = normalizeText(responseText);
|
|
262
|
+
if (normalizedResponse) return `text:${normalizedResponse}`;
|
|
263
|
+
|
|
264
|
+
const labels = uniqueStrings([...artifactLabels, ...suspiciousArtifactLabels].map((value) => normalizeText(value))).sort((left, right) => left.localeCompare(right));
|
|
265
|
+
if (labels.length > 0) return `artifacts:${labels.join("|")}`;
|
|
266
|
+
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param {DerivedCompletionSignatureArgs} args
|
|
272
|
+
* @returns {string | undefined}
|
|
273
|
+
*/
|
|
274
|
+
export function deriveAssistantCompletionSignature({
|
|
275
|
+
hasStopStreaming,
|
|
276
|
+
hasTargetCopyResponse,
|
|
277
|
+
responseText,
|
|
278
|
+
artifactLabels = [],
|
|
279
|
+
suspiciousArtifactLabels = [],
|
|
280
|
+
}) {
|
|
281
|
+
if (hasStopStreaming) return undefined;
|
|
282
|
+
|
|
283
|
+
if (hasTargetCopyResponse && normalizeText(responseText)) {
|
|
284
|
+
return buildAssistantCompletionSignature({ responseText });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!normalizeText(responseText)) {
|
|
288
|
+
return buildAssistantCompletionSignature({ responseText, artifactLabels, suspiciousArtifactLabels });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|