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.
Files changed (37) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +7 -0
  3. package/docs/ORACLE_DESIGN.md +1 -1
  4. package/docs/ORACLE_ISOLATED_PI_VALIDATION.md +249 -0
  5. package/docs/ORACLE_RECOVERY_DRILL.md +5 -4
  6. package/extensions/oracle/index.ts +8 -1
  7. package/extensions/oracle/lib/commands.ts +11 -24
  8. package/extensions/oracle/lib/config.ts +5 -0
  9. package/extensions/oracle/lib/jobs.ts +117 -217
  10. package/extensions/oracle/lib/locks.ts +41 -209
  11. package/extensions/oracle/lib/poller.ts +14 -51
  12. package/extensions/oracle/lib/queue.ts +75 -112
  13. package/extensions/oracle/lib/runtime.ts +60 -14
  14. package/extensions/oracle/lib/tools.ts +70 -67
  15. package/extensions/oracle/shared/job-coordination-helpers.d.mts +84 -0
  16. package/extensions/oracle/shared/job-coordination-helpers.mjs +168 -0
  17. package/extensions/oracle/shared/job-lifecycle-helpers.d.mts +130 -0
  18. package/extensions/oracle/shared/job-lifecycle-helpers.mjs +377 -0
  19. package/extensions/oracle/shared/job-observability-helpers.d.mts +59 -0
  20. package/extensions/oracle/shared/job-observability-helpers.mjs +143 -0
  21. package/extensions/oracle/shared/process-helpers.d.mts +20 -0
  22. package/extensions/oracle/shared/process-helpers.mjs +128 -0
  23. package/extensions/oracle/shared/state-coordination-helpers.d.mts +43 -0
  24. package/extensions/oracle/shared/state-coordination-helpers.mjs +381 -0
  25. package/extensions/oracle/worker/artifact-heuristics.mjs +5 -0
  26. package/extensions/oracle/worker/auth-bootstrap.mjs +100 -139
  27. package/extensions/oracle/worker/auth-cookie-policy.mjs +5 -0
  28. package/extensions/oracle/worker/auth-flow-helpers.d.mts +41 -0
  29. package/extensions/oracle/worker/auth-flow-helpers.mjs +165 -0
  30. package/extensions/oracle/worker/chatgpt-flow-helpers.d.mts +13 -0
  31. package/extensions/oracle/worker/chatgpt-flow-helpers.mjs +85 -0
  32. package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +33 -0
  33. package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +292 -0
  34. package/extensions/oracle/worker/run-job.mjs +235 -380
  35. package/extensions/oracle/worker/state-locks.mjs +31 -216
  36. package/package.json +14 -5
  37. 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
+ }