pi-oracle 0.6.16 → 0.6.17

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 CHANGED
@@ -2,8 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.17 - 2026-05-10
6
+
5
7
  ### Changed
6
8
  - made `/oracle-auth` success and failure output easier to scan, with compact source summaries and source-specific troubleshooting for configured Chromium cookie sources
9
+ - tightened README quickstart/command wording around preflight-first `/oracle` behavior, context-rich archive selection, cleanup retention, and preset defaults
10
+ - added the resolved oracle model preset snapshot to `oracle_submit` queued/dispatched output so agents can see what preset will run
11
+ - clarified `oracle_preflight` output so users can see that it validates the persisted pi session, local config, and ChatGPT auth seed created by `oracle_auth`
12
+ - made direct `scripts/oracle-sanity.ts` execution fail fast unless isolated oracle state/jobs dirs are provided, preventing accidental sanity-job leakage into live oracle pollers
13
+
14
+ ### Fixed
15
+ - stopped worker/auth readiness checks from treating ChatGPT's public logged-out composer shell as an authenticated ready state, and made stale-auth failures name the visible login controls instead of reporting a misleading partial-login state
16
+ - made plain `instant` model selection robust against the current ChatGPT model menu by recognizing closed Instant chips and falling back to the top-level model menu when the configure sheet is unavailable
7
17
 
8
18
  ## 0.6.16 - 2026-05-07
9
19
 
package/README.md CHANGED
@@ -1,62 +1,118 @@
1
1
  # pi-oracle
2
2
 
3
- `pi-oracle` is a `pi` package that lets the agent hand off difficult, long-running tasks to ChatGPT.com through the web app instead of the API.
3
+ `pi-oracle` lets a `pi` agent send hard, long-running work to ChatGPT.com through the web app, with repo archives, background execution, saved results, and a best-effort wake-up back into `pi` when the answer is ready.
4
4
 
5
- Use it when you want:
6
- - your real ChatGPT account
7
- - web-model behavior instead of API usage
8
- - large repo/context uploads
9
- - async background execution
10
- - durable saved responses/artifacts plus best-effort wake-ups back into `pi`
5
+ > Status: experimental public beta. Validated primarily on macOS with Google Chrome/Chromium and `pi` 0.65.0+. Normal oracle jobs run in an isolated browser profile, not your active browser window.
11
6
 
12
- Normal oracle jobs run in an isolated browser profile, not your active browser window.
7
+ ## What a successful run looks like
13
8
 
14
- > Status: experimental public beta. Validated primarily on macOS with Google Chrome and `pi` 0.65.0+.
9
+ ```text
10
+ You: /oracle Review the pending changes. Include the whole repo unless a narrower archive is clearly better.
11
+
12
+ pi-oracle:
13
+ 1. preflights local session/auth readiness
14
+ 2. builds a context-rich `.tar.zst` repo archive
15
+ 3. starts an isolated ChatGPT web runtime in the background
16
+ 4. uploads the archive and prompt to ChatGPT.com
17
+ 5. saves the response/artifacts under /tmp/oracle-<job-id>/
18
+ 6. sends a best-effort wake-up back to the matching pi session
19
+
20
+ Later: /oracle-read <job-id>
21
+ ```
22
+
23
+ What you are seeing: the local `pi` agent keeps control of context selection and safety checks, while ChatGPT web handles the expensive second-opinion work asynchronously. If the wake-up is missed, the result still lives on disk and can be read by job id.
24
+
25
+ ## Who this is for
26
+
27
+ Use `pi-oracle` if you use `pi` and want a larger asynchronous reviewer, planner, or analyst that can use your real ChatGPT web account instead of an API model.
28
+
29
+ It is most useful for:
30
+
31
+ - maintainers reviewing broad repo changes before shipping
32
+ - agents that need a slower second opinion without blocking the main `pi` turn
33
+ - migration, architecture, or failure-mode analysis that benefits from a large archive
34
+ - follow-up questions that should continue the same ChatGPT thread later
35
+
36
+ Do not use it for:
37
+
38
+ - short local coding tasks that `pi` can handle directly
39
+ - projects that must never be uploaded to ChatGPT.com
40
+ - non-macOS environments or machines without the required local browser/tooling
41
+
42
+ ## Problem it solves
15
43
 
16
- ## When to use it
44
+ A normal coding-agent turn is the wrong shape for some work: the task may need a large repo snapshot, a slower web model, a real ChatGPT subscription, artifact downloads, or a durable result that arrives after the active turn ends.
17
45
 
18
- Use `pi-oracle` for:
19
- - big code reviews of a repo or pending changes
20
- - architectural or migration analysis that benefits from a large uploaded archive
21
- - long-running prompts that may take minutes to finish
22
- - follow-up questions in the same ChatGPT thread later
46
+ `pi-oracle` makes that workflow explicit and recoverable instead of asking the main agent to manually drive ChatGPT in your real browser window.
23
47
 
24
- Do not reach for it first for:
25
- - normal short coding tasks that `pi` can handle directly
26
- - workflows that must never upload project archives to ChatGPT.com
27
- - environments outside the current supported setup
48
+ ## What it does
28
49
 
29
- ## Install
50
+ | Problem | Capability | Proof in this repo |
51
+ | --- | --- | --- |
52
+ | Hard tasks need more context than a quick turn should gather. | `/oracle` prompts the agent to preflight, choose a context-rich archive, and submit it to ChatGPT web. | [`prompts/oracle.md`](prompts/oracle.md), `oracle_submit`, archive tests in `scripts/oracle-sanity-*` |
53
+ | Browser automation should not steal focus or mutate your active profile. | Jobs clone an authenticated seed profile into per-job isolated runtime profiles. | [`docs/ORACLE_DESIGN.md`](docs/ORACLE_DESIGN.md), [`extensions/oracle/lib/runtime.ts`](extensions/oracle/lib/runtime.ts) |
54
+ | Long jobs need durability. | Job state, responses, logs, and artifacts persist under `${PI_ORACLE_JOBS_DIR:-/tmp}/oracle-<job-id>/`. | [`extensions/oracle/lib/jobs.ts`](extensions/oracle/lib/jobs.ts), `/oracle-read`, `/oracle-status` |
55
+ | ChatGPT auth can expire or drift. | `/oracle-auth` refreshes the isolated auth seed from a configured local Chromium profile, with recovery guidance. | [`extensions/oracle/lib/auth.ts`](extensions/oracle/lib/auth.ts), [`docs/ORACLE_RECOVERY_DRILL.md`](docs/ORACLE_RECOVERY_DRILL.md) |
56
+ | Agents need a simple API, not UI-driving instructions. | The package exposes agent-facing tools: `oracle_preflight`, `oracle_submit`, `oracle_read`, `oracle_auth`, and `oracle_cancel`. | [`extensions/oracle/lib/tools.ts`](extensions/oracle/lib/tools.ts) |
30
57
 
31
- npm:
58
+ ## Fastest way to see it work
59
+
60
+ ### 1. Install
61
+
62
+ From npm:
32
63
 
33
64
  ```bash
34
65
  pi install npm:pi-oracle
35
66
  ```
36
67
 
37
- GitHub:
68
+ Or from GitHub:
38
69
 
39
70
  ```bash
40
71
  pi install https://github.com/fitchmultz/pi-oracle
41
72
  ```
42
73
 
43
- ## Quickstart
74
+ ### 2. Check requirements
75
+
76
+ You need:
77
+
78
+ - macOS
79
+ - Node.js 22 or newer
80
+ - `pi` 0.65.0 or newer
81
+ - Google Chrome or another Chromium-family browser
82
+ - ChatGPT already signed in to the configured local browser profile
83
+ - `agent-browser`, `tar`, and `zstd` available on the machine
84
+ - a normal persisted `pi` session, not `pi --no-session`
85
+
86
+ ### 3. Sync ChatGPT auth once
87
+
88
+ ```text
89
+ /oracle-auth
90
+ ```
91
+
92
+ This reads ChatGPT cookies from your configured local browser profile and writes an isolated oracle seed profile. It should not automate your active browser window for normal jobs.
44
93
 
45
- 1. Start a normal persisted `pi` session. Do not use `pi --no-session` for oracle.
46
- 2. Make sure ChatGPT already works in your configured local browser profile.
47
- 3. Make sure these are installed: Google Chrome, `agent-browser`, `tar`, and `zstd`.
48
- 4. Optional: create `~/.pi/agent/extensions/oracle.json` if you want non-default settings.
49
- 5. Run `/oracle-auth`.
50
- 6. Run `/oracle Review the current pending changes. Include the whole repo unless a narrower archive is clearly better.`
51
- 7. Wait for the one-time best-effort wake-up, or check `/oracle-status`.
94
+ ### 4. Submit a tiny job
52
95
 
53
- The `/oracle` prompt now runs an early oracle preflight before it gathers repo context, so missing persisted-session or local auth/config blockers fail before the agent spends time reading files.
96
+ ```text
97
+ /oracle Read README.md and package.json. Tell me in five bullets what this package does and who should not use it.
98
+ ```
99
+
100
+ Expected result:
54
101
 
55
- For explicitly narrow requests, `/oracle` should still prefer a context-rich relevant archive up to the 250 MB ceiling, including nearby tests, docs, config, and adjacent modules when that can improve answer quality. Reserve tightly minimal archives for an explicit user request for a tight archive, privacy-sensitive material, or size-constrained cases. It should also omit `preset` and use the configured default model unless the task clearly needs a different one.
102
+ - The `/oracle` prompt now runs an early oracle preflight before expensive repo reading or archive creation.
103
+ - The agent chooses a context-rich relevant archive up to the 250 MB ceiling, not the smallest possible one-file slice when nearby context helps.
104
+ - `oracle_submit` creates or queues a job.
105
+ - If local packing is too large, the prompt treats that as a retryable archive-selection failure and narrows automatically before surfacing the problem.
106
+ - The job uploads a repo archive to ChatGPT.com, capped at 250 MiB after default exclusions/pruning.
107
+ - The response is saved under `/tmp/oracle-<job-id>/response.md` by default.
108
+ - The matching `pi` session gets one best-effort wake-up when the job finishes.
56
109
 
57
- If a local archive still exceeds the 250 MB limit after default exclusions and automatic whole-repo pruning, the agent should treat that as a retryable archive-selection failure: shrink the archive automatically, retry with a smaller relevant slice, and explain what it cut only if it still cannot fit after the allowed retry budget.
110
+ If the wake-up does not arrive, run:
58
111
 
59
- If you miss the one-time wake-up, the result is still saved durably in the oracle job directory and can be read later.
112
+ ```text
113
+ /oracle-status
114
+ /oracle-read <job-id>
115
+ ```
60
116
 
61
117
  ## Example requests
62
118
 
@@ -73,46 +129,53 @@ If you miss the one-time wake-up, the result is still saved durably in the oracl
73
129
  ```
74
130
 
75
131
  ```text
76
- /oracle-followup <job-id> Tighten the migration plan around rollback risk, and include the most relevant surrounding files/docs as long as the archive stays comfortably within the limit.
132
+ /oracle-followup <job-id> Tighten the migration plan around rollback risk, and include the most relevant surrounding files/docs as long as the archive stays comfortably within the 250 MiB limit.
77
133
  ```
78
134
 
79
- After a job finishes, use `/oracle-followup <job-id> <request>` to continue the same ChatGPT thread without hand-writing the low-level `followUpJobId` tool parameter.
80
-
81
- ## High-level flow
135
+ ## How it works
82
136
 
83
137
  ```mermaid
84
138
  flowchart LR
85
139
  A["/oracle request"] --> B["Agent preflights, then gathers a context-rich relevant repo slice"]
86
- B --> C["oracle_submit builds archive"]
87
- C --> D["Detached worker starts isolated ChatGPT runtime"]
88
- D --> E["Archive + prompt sent to ChatGPT.com"]
89
- E --> F["Response/artifacts saved under oracle job dir"]
90
- F --> G["One-time best-effort wake-up to matching pi session"]
140
+ B --> C["Agent chooses context-rich archive inputs"]
141
+ C --> D["oracle_submit builds .tar.zst archive"]
142
+ D --> E["Detached worker clones isolated auth seed profile"]
143
+ E --> F["ChatGPT web receives archive + prompt"]
144
+ F --> G["Response/artifacts saved under oracle job dir"]
145
+ G --> H["Best-effort wake-up to matching pi session"]
91
146
  ```
92
147
 
93
- If concurrency is full, the job is queued and starts automatically later.
148
+ Key design choices:
94
149
 
95
- ## What the package adds
150
+ - **Prompt templates own context gathering.** `/oracle` and `/oracle-followup` tell the agent how to preflight, gather context, choose archive inputs, and then stop after dispatch.
151
+ - **Tools own execution.** `oracle_submit` builds the archive, admits or queues the job, starts the worker, and returns immediately.
152
+ - **Auth uses a seed profile.** `/oracle-auth` imports cookies into an isolated seed profile; each job clones that seed into its own temporary runtime profile.
153
+ - **Follow-ups preserve ChatGPT thread state.** `/oracle-followup <job-id> ...` resolves the prior job's saved ChatGPT URL and submits the next prompt with `followUpJobId`.
154
+ - **Wake-up is best effort, storage is durable.** A missed wake-up does not lose the result.
155
+
156
+ ## Commands and tools
96
157
 
97
158
  User-facing commands:
98
- - `/oracle <request>` — prompt template that tells the agent to gather context and dispatch an oracle job
99
- - `/oracle-followup <job-id> <request>` — prompt template that continues an earlier oracle job in the same ChatGPT thread
100
- - `/oracle-auth` sync ChatGPT cookies from your configured local browser profile into the isolated oracle auth profile
101
- - `/oracle-read [job-id]` — inspect job status plus the saved response preview
102
- - `/oracle-status [job-id]` — inspect job status and list recent job ids when no explicit id is given
103
- - `/oracle-cancel <job-id>`cancel a queued or active job by id
104
- - `/oracle-clean <job-id|all>` — remove temp files for terminal jobs; recently woken terminal jobs may stay retained briefly and return a retry-after hint
159
+
160
+ - `/oracle <request>` — prepare context and dispatch a ChatGPT web oracle job
161
+ - `/oracle-followup <job-id> <request>` continue an earlier oracle job in the same ChatGPT thread
162
+ - `/oracle-auth` — sync ChatGPT cookies into the isolated oracle auth seed profile
163
+ - `/oracle-read [job-id]` — inspect job status and saved response preview
164
+ - `/oracle-status [job-id]`inspect a job or list recent job ids when no explicit id is given
165
+ - `/oracle-cancel <job-id>` — cancel a queued or active job
166
+ - `/oracle-clean <job-id|all>` — remove temp files for terminal jobs; recently woken terminal jobs may stay retained briefly, and a blocked cleanup returns the next eligible cleanup time
105
167
 
106
168
  Agent-facing tools:
169
+
107
170
  - `oracle_preflight`
108
171
  - `oracle_auth`
109
172
  - `oracle_submit`
110
173
  - `oracle_read`
111
174
  - `oracle_cancel`
112
175
 
113
- ## Minimal config
176
+ ## Configuration
114
177
 
115
- Most users can start with the packaged defaults and only set the browser profile if needed.
178
+ Most users can start with defaults. Set an agent-level config only when you need a non-default preset or browser profile.
116
179
 
117
180
  `~/.pi/agent/extensions/oracle.json`
118
181
 
@@ -128,14 +191,16 @@ Most users can start with the packaged defaults and only set the browser profile
128
191
  ```
129
192
 
130
193
  Notes:
194
+
131
195
  - `defaults.preset` is the default ChatGPT model preset for oracle jobs.
132
- - The canonical preset ids live in `extensions/oracle/lib/config.ts`.
133
- - If the packaged default is fine, you can omit `defaults.preset` entirely.
134
- - You usually do not need to set browser paths unless auto-detection fails.
196
+ - The canonical preset ids live in [`extensions/oracle/lib/config.ts`](extensions/oracle/lib/config.ts).
197
+ - If the packaged default is fine, omit `defaults.preset`.
198
+ - When an agent is unsure which oracle preset fits, it should omit `preset` and use the configured default model instead of asking by default.
199
+ - You usually do not need browser paths unless auto-detection fails.
135
200
 
136
201
  ### Custom Chromium cookie sources
137
202
 
138
- Most Chrome-compatible browsers should work through the default cookie importer. Use this alternate path only for a Chromium-family browser that is not one of `@steipete/sweet-cookie`'s built-in Chrome/Brave/Arc/Chromium targets or otherwise cannot import cookies without dependency patching.
203
+ Use this only for a Chromium-family browser that the default cookie importer cannot read.
139
204
 
140
205
  Before running `/oracle-auth` with this path:
141
206
 
@@ -143,7 +208,7 @@ Before running `/oracle-auth` with this path:
143
208
  2. Fully quit the browser so its `Cookies` database is stable.
144
209
  3. Find the profile `Cookies` SQLite DB path.
145
210
  4. Find the browser's macOS Keychain safe-storage item account and service name.
146
- 5. Configure all of `browser.executablePath`, `auth.chromeCookiePath`, and `auth.chromiumKeychain` in the agent-level config at `~/.pi/agent/extensions/oracle.json`.
211
+ 5. Configure all of `browser.executablePath`, `auth.chromeCookiePath`, and `auth.chromiumKeychain` in `~/.pi/agent/extensions/oracle.json`.
147
212
 
148
213
  Example Helium config:
149
214
 
@@ -164,9 +229,7 @@ Example Helium config:
164
229
  }
165
230
  ```
166
231
 
167
- `auth.chromeCookiePath` remains the cookie database path for backward compatibility. `auth.chromiumKeychain` must be paired with `auth.chromeCookiePath`; partial config is rejected so oracle does not silently fall back to a different browser source. When both are present, `/oracle-auth` uses pi-oracle's repo-owned generic Chromium cookie reader instead of patching `@steipete/sweet-cookie` internals.
168
-
169
- If macOS prompts for Keychain access during `/oracle-auth`, allow access for the configured browser safe-storage item. If auth still fails after cookies are synced, the cookie DB may be stale, from the wrong profile, or for an account that is logged out; reopen the configured browser profile, confirm ChatGPT works there, quit the browser, and rerun `/oracle-auth`.
232
+ `auth.chromeCookiePath` must be paired with `auth.chromiumKeychain`; partial config is rejected so oracle does not silently fall back to another browser source.
170
233
 
171
234
  ## Available presets
172
235
 
@@ -181,38 +244,34 @@ If macOS prompts for Keychain access during `/oracle-auth`, allow access for the
181
244
  | `instant` | Instant |
182
245
  | `instant_auto_switch` | Instant - Auto-switch to Thinking Enabled |
183
246
 
184
- `oracle_submit` accepts either the canonical preset id or the matching human-readable preset label; common space/hyphen variants are normalized automatically at submit time. Keep `defaults.preset` in config on the canonical preset id.
247
+ `oracle_submit` accepts canonical preset ids or a matching human-readable preset label. Keep config values on canonical ids.
185
248
 
186
- Other useful settings:
187
- - `browser.runMode`
188
- - `browser.args`
189
- - `browser.authSeedProfileDir`
190
- - `browser.runtimeProfilesDir`
191
- - `cleanup.completeJobRetentionMs`
192
- - `cleanup.failedJobRetentionMs`
249
+ ## Outputs and cleanup
193
250
 
194
- Project config should only override safe, non-privileged settings.
251
+ - Jobs persist response text, metadata, logs, and artifacts under `${PI_ORACLE_JOBS_DIR:-/tmp}/oracle-<job-id>/` by default.
252
+ - Jobs can queue automatically when runtime capacity is full.
253
+ - Completion delivery into `pi` is one-time best-effort wake-up based.
254
+ - `/oracle-read [job-id]` and `oracle_read({ jobId })` inspect saved output later.
255
+ - `/oracle-clean` removes terminal job temp files, but can briefly refuse cleanup after a wake-up so the follow-up turn can still read the saved paths.
195
256
 
196
- ## What happens to outputs
257
+ ## Privacy and local data
197
258
 
198
- - Jobs persist their response and any artifacts under `${PI_ORACLE_JOBS_DIR:-/tmp}/oracle-<job-id>/` by default.
199
- - Jobs can queue automatically if runtime capacity is full.
200
- - Completion delivery into `pi` is one-time best-effort wake-up based; duplicate poller scans are deduped in job state.
201
- - If you miss the wake-up, use `/oracle-read [job-id]` to inspect the saved response preview.
202
- - `/oracle-status [job-id]` still shows saved job metadata and lists recent job ids when you omit the id.
203
- - Agent callers can use `oracle_read({ jobId })`.
204
- - If a prior oracle run failed because ChatGPT login was required or the worker explicitly said to rerun `/oracle-auth`, agent callers can run `oracle_auth({})` once and then retry the submission once.
205
- - `/oracle-clean` can still refuse a terminal job briefly after a wake-up send so saved response/artifact paths survive the follow-up turn; when that guard applies, it returns the next eligible cleanup time.
259
+ This extension is local-first, but it handles sensitive local and project data:
206
260
 
207
- ## Requirements
261
+ - `/oracle-auth` reads ChatGPT cookies from the configured local browser profile.
262
+ - `oracle_submit` uploads selected project archives to ChatGPT.com.
263
+ - Responses, logs, and artifacts are written to the configured oracle jobs directory.
208
264
 
209
- - macOS
210
- - Node.js 22 or newer
211
- - Google Chrome or another Chromium-family browser installed
212
- - ChatGPT already signed into the configured local browser profile
213
- - `pi` 0.65.0 or newer
214
- - `agent-browser` available on the machine
215
- - `tar` and `zstd` available
265
+ Review the code and design docs before using it with private or regulated material.
266
+
267
+ ## Current limits
268
+
269
+ - Experimental public beta, validated primarily on macOS.
270
+ - ChatGPT UI, auth, model controls, and artifact download behavior can drift.
271
+ - Archive uploads are capped at 250 MiB after default exclusions and automatic whole-repo pruning.
272
+ - A real ChatGPT web session is required.
273
+ - The README currently uses command-level proof and design docs; no public screenshot or demo GIF is checked into the repo.
274
+ - Production hardening should keep focusing on UI drift detection, auth recovery, artifact capture, and environment diagnostics.
216
275
 
217
276
  ## Troubleshooting
218
277
 
@@ -221,6 +280,7 @@ Project config should only override safe, non-privileged settings.
221
280
  - Make sure ChatGPT works in the same local browser profile you configured.
222
281
  - For custom Chromium cookie sources, confirm `auth.chromeCookiePath` points at that profile's `Cookies` DB and `auth.chromiumKeychain.services` names the browser's safe-storage Keychain service.
223
282
  - Re-run `/oracle-auth`.
283
+ - Agent callers can use `oracle_auth({})` once before retrying a stale-auth oracle submission.
224
284
  - If ChatGPT is half-logged-in or challenge flow state looks weird, finish the login/challenge in the headed auth browser and retry.
225
285
 
226
286
  ### Custom Chromium auth says cookies synced but the session is rejected
@@ -234,10 +294,10 @@ This usually means the cookie import worked but the source cookies are not the a
234
294
  5. Confirm `auth.chromiumKeychain.services` names the browser's safe-storage Keychain service for that DB.
235
295
  6. Re-run `/oracle-auth`.
236
296
 
237
- ### You hit a challenge / verification page
297
+ ### You hit a challenge or verification page
238
298
 
239
299
  - Solve it in the auth/bootstrap browser if prompted.
240
- - Then re-run `/oracle-auth` before submitting jobs again.
300
+ - Re-run `/oracle-auth` before submitting jobs again.
241
301
 
242
302
  ### You see "Oracle requires a persisted pi session"
243
303
 
@@ -247,19 +307,19 @@ This usually means the cookie import worked but the source cookies are not the a
247
307
  ### A job finished but no wake-up arrived
248
308
 
249
309
  - Use `/oracle-read [job-id]` to inspect the saved response preview.
250
- - Use `/oracle-status [job-id]` when you want status metadata or need help finding a job id.
251
- - Agent callers can use `oracle_read({ jobId })` if they need tool output in the current turn.
310
+ - Use `/oracle-status` if you need help finding a recent job id.
311
+ - Agent callers can use `oracle_read({ jobId })`.
252
312
  - Results are still saved on disk even if the reminder turn does not land.
253
313
 
254
314
  ### `/oracle-clean` refuses a terminal job right after completion
255
315
 
256
316
  - This can happen during the short post-send retention grace window after a wake-up was sent.
257
- - The command now returns a `Retry after ...` timestamp when that guard is active.
258
- - Wait until that time, then rerun `/oracle-clean [job-id|all]`.
317
+ - The command returns a `Retry after ...` timestamp when that guard is active.
318
+ - Wait until that time, then rerun `/oracle-clean <job-id|all>`.
259
319
 
260
320
  ### `agent-browser`, `tar`, or `zstd` is missing
261
321
 
262
- - Install the missing local dependency and rerun the command.
322
+ Install the missing local dependency and rerun the command.
263
323
 
264
324
  ### Auto-detection picked the wrong browser profile
265
325
 
@@ -269,25 +329,11 @@ This usually means the cookie import worked but the source cookies are not the a
269
329
 
270
330
  ### You want more details about a failed run
271
331
 
272
- - Inspect the job directory under `${PI_ORACLE_JOBS_DIR:-/tmp}/oracle-<job-id>/`.
273
- - The worker log and captured diagnostics are stored there.
332
+ Inspect the job directory under `${PI_ORACLE_JOBS_DIR:-/tmp}/oracle-<job-id>/`. The worker log and captured diagnostics are stored there.
274
333
 
275
- ## Detailed docs
334
+ ## Verification
276
335
 
277
- - `docs/ORACLE_DESIGN.md` — architecture, lifecycle, queueing, persistence, presets, and recovery behavior
278
- - `docs/ORACLE_RECOVERY_DRILL.md` — safe expired-auth recovery validation drill
279
- - `docs/ORACLE_ISOLATED_PI_VALIDATION.md` — repeatable isolated `pi` session smoke test for local-extension validation
280
-
281
- ## Privacy / local data
282
-
283
- This extension is local-first, but it does read and persist local data:
284
- - `/oracle-auth` reads ChatGPT cookies from the configured local browser profile
285
- - job archives are uploaded to ChatGPT.com
286
- - responses and artifacts are written under the configured oracle jobs dir
287
-
288
- Review the code and design docs before using it with sensitive material.
289
-
290
- ## Validation helpers
336
+ Useful local checks:
291
337
 
292
338
  ```bash
293
339
  npm run check:oracle-extension
@@ -295,21 +341,32 @@ npm run typecheck
295
341
  npm run typecheck:worker-helpers
296
342
  npm run sanity:oracle
297
343
  npm run pack:check
298
- # conventional local gate
299
344
  npm test
300
- # or all at once
301
345
  npm run verify:oracle
302
346
  ```
303
347
 
304
- `npm publish` is also guarded locally via `prepublishOnly` and will run `npm run verify:oracle` before publishing.
348
+ `npm publish` is guarded by `prepublishOnly`, which runs `npm run verify:oracle`.
349
+
350
+ For end-to-end local-extension smoke testing, use [`docs/ORACLE_ISOLATED_PI_VALIDATION.md`](docs/ORACLE_ISOLATED_PI_VALIDATION.md). That workflow launches isolated `pi` sessions against this checkout and uses `instant` or `thinking_light`, as required by the project validation policy.
305
351
 
306
- ## Beta caveats
352
+ ## Project map
307
353
 
308
- The highest-risk areas to monitor are:
309
- - ChatGPT UI drift
310
- - auth/bootstrap drift
311
- - artifact download behavior
312
- - local environment assumptions
354
+ | Path | Purpose |
355
+ | --- | --- |
356
+ | [`extensions/oracle/index.ts`](extensions/oracle/index.ts) | Extension entrypoint |
357
+ | `extensions/oracle/lib/` | Commands, tools, config, jobs, queueing, runtime, poller |
358
+ | `extensions/oracle/worker/` | Detached ChatGPT web worker and UI/auth helpers |
359
+ | `extensions/oracle/shared/` | Shared process, state, job, and observability helpers |
360
+ | [`prompts/oracle.md`](prompts/oracle.md) | `/oracle` prompt-template workflow |
361
+ | [`prompts/oracle-followup.md`](prompts/oracle-followup.md) | `/oracle-followup` prompt-template workflow |
362
+ | `scripts/oracle-sanity-*` | Local sanity and archive-safety checks |
363
+ | [`docs/ORACLE_DESIGN.md`](docs/ORACLE_DESIGN.md) | Architecture, lifecycle, queueing, persistence, recovery behavior |
364
+ | [`docs/ORACLE_ISOLATED_PI_VALIDATION.md`](docs/ORACLE_ISOLATED_PI_VALIDATION.md) | Repeatable isolated `pi` validation workflow |
365
+ | [`docs/ORACLE_RECOVERY_DRILL.md`](docs/ORACLE_RECOVERY_DRILL.md) | Safe expired-auth recovery drill |
366
+
367
+ ## Next action
368
+
369
+ Install the package, run `/oracle-auth`, then submit the tiny README/package review job above. If you are evaluating the design before running it, start with [`docs/ORACLE_DESIGN.md`](docs/ORACLE_DESIGN.md).
313
370
 
314
371
  ## License
315
372
 
@@ -957,12 +957,14 @@ function formatOraclePreflightResponse(details: OraclePreflightDetails): string
957
957
  "Oracle preflight ready.",
958
958
  details.session.sessionFile ? `Persisted session: ${details.session.sessionFile}` : undefined,
959
959
  details.auth.seedProfileDir ? `Auth seed profile: ${details.auth.seedProfileDir}` : undefined,
960
+ "Preflight validates the persisted pi session, local oracle config, and ChatGPT auth seed created by oracle_auth.",
960
961
  "You can continue with oracle context gathering and submission.",
961
962
  ].filter(Boolean).join("\n");
962
963
  }
963
964
 
964
965
  return [
965
966
  `Oracle preflight blocked: ${details.error?.message ?? "unknown blocker"}`,
967
+ "Preflight checks the persisted pi session, local oracle config, and ChatGPT auth seed before any archive work starts.",
966
968
  details.error?.suggestedNextStep ? `Suggested next step: ${details.error.suggestedNextStep}` : undefined,
967
969
  ].filter(Boolean).join("\n");
968
970
  }
@@ -12,6 +12,12 @@ export interface OracleJobSummaryLike {
12
12
  heartbeatAt?: string;
13
13
  projectId: string;
14
14
  sessionId: string;
15
+ selection?: {
16
+ preset?: string;
17
+ modelFamily?: string;
18
+ effort?: string;
19
+ autoSwitchToThinking?: boolean;
20
+ };
15
21
  followUpToJobId?: string;
16
22
  chatUrl?: string;
17
23
  conversationId?: string;
@@ -46,6 +46,20 @@ function formatAutoPrunedArchiveMessage(autoPrunedPrefixes) {
46
46
  return `Archive auto-pruned generic generated-output-name dirs to fit size limit: ${autoPrunedPrefixes.map((entry) => `${entry.relativePath}/ (${formatBytes(entry.bytes)})`).join(", ")}`;
47
47
  }
48
48
 
49
+ /**
50
+ * @param {OracleJobSummaryLike["selection"]} selection
51
+ * @returns {string | undefined}
52
+ */
53
+ function formatOracleSelection(selection) {
54
+ if (!selection?.preset || !selection.modelFamily) return undefined;
55
+ const details = [
56
+ `family=${selection.modelFamily}`,
57
+ selection.effort ? `effort=${selection.effort}` : undefined,
58
+ selection.autoSwitchToThinking === true ? "auto-switch-to-thinking=true" : undefined,
59
+ ].filter(Boolean).join(", ");
60
+ return `Model preset: ${selection.preset}${details ? ` (${details})` : ""}`;
61
+ }
62
+
49
63
  const ACTIVE_SUMMARY_STATUSES = new Set(["preparing", "submitted", "waiting"]);
50
64
  const DEFAULT_ORACLE_HEARTBEAT_STALE_MS = 3 * 60 * 1000;
51
65
  const DEFAULT_ACTIVE_JOB_POLL_HINT_SECONDS = 15;
@@ -221,6 +235,7 @@ export function formatOracleSubmitResponse(job, options) {
221
235
  `${options.queued ? "Oracle job queued" : "Oracle job dispatched"}: ${job.id}`,
222
236
  options.queued && options.queuePosition && options.queueDepth ? `Queue position: ${options.queuePosition} of ${options.queueDepth}` : undefined,
223
237
  job.followUpToJobId ? `Follow-up to: ${job.followUpToJobId}` : undefined,
238
+ formatOracleSelection(job.selection),
224
239
  `Prompt: ${job.promptPath}`,
225
240
  `Archive: ${job.archivePath}`,
226
241
  formatAutoPrunedArchiveMessage(options.autoPrunedPrefixes),
@@ -109,7 +109,9 @@ export function classifyChatAuthPage(args) {
109
109
  return { state: "transient_outage_error", message: `ChatGPT is showing a transient outage/error page. Logs: ${args.logPath}` };
110
110
  }
111
111
 
112
- if (args.probe?.status === 401 || (args.probe?.status === 403 && (!onAllowedOrigin || !hasUsableComposer || args.probe?.domLoginCta))) {
112
+ const probeHasAccountIdentity = args.probe?.bodyHasId === true || args.probe?.bodyHasEmail === true;
113
+
114
+ if (args.probe?.status === 401 || (args.probe?.status === 403 && (!onAllowedOrigin || !hasUsableComposer))) {
113
115
  return {
114
116
  state: "login_required",
115
117
  message:
@@ -120,7 +122,7 @@ export function classifyChatAuthPage(args) {
120
122
  }
121
123
 
122
124
  if (args.probe?.onAuthPage) {
123
- if (args.probe?.bodyHasId || args.probe?.bodyHasEmail) {
125
+ if (probeHasAccountIdentity) {
124
126
  return {
125
127
  state: "auth_transitioning",
126
128
  message:
@@ -137,6 +139,16 @@ export function classifyChatAuthPage(args) {
137
139
  };
138
140
  }
139
141
 
142
+ if (onAllowedOrigin && hasUsableComposer && args.probe?.domLoginCta && !probeHasAccountIdentity) {
143
+ return {
144
+ state: "login_required",
145
+ message:
146
+ `ChatGPT still shows public login controls after syncing cookies from ${args.cookieSourceLabel}. ` +
147
+ `The cookie DB may be stale, from the wrong browser profile, or for an account that is logged out. ` +
148
+ `Check auth.chromeProfile/auth.chromeCookiePath/auth.chromiumKeychain and inspect ${args.logPath}.`,
149
+ };
150
+ }
151
+
140
152
  if (onAllowedOrigin && (args.probe?.status === 200 || args.probe?.status === 403) && hasUsableComposer) {
141
153
  if (!args.probe?.domLoginCta) {
142
154
  return {
@@ -145,12 +157,12 @@ export function classifyChatAuthPage(args) {
145
157
  };
146
158
  }
147
159
 
160
+ // The public logged-out composer case returned above, so a remaining visible login CTA here still has account-like probe data.
148
161
  return {
149
162
  state: "auth_transitioning",
150
163
  message:
151
- args.probe?.bodyHasId || args.probe?.bodyHasEmail
152
- ? `ChatGPT backend session is authenticated but the shell still shows public CTA chrome. Logs: ${args.logPath}`
153
- : `ChatGPT accepted cookies but is still hydrating/auth-selecting. Logs: ${args.logPath}`,
164
+ `ChatGPT backend probe returned account-like fields, but the shell still shows public login controls. ` +
165
+ `Trying to resolve the account shell. Logs: ${args.logPath}`,
154
166
  };
155
167
  }
156
168
 
@@ -30,6 +30,7 @@ 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
32
  const BARE_EFFORT_PATTERN = /^(light|standard|extended|heavy)(?:, click to remove)?$/i;
33
+ const INSTANT_CHIP_PATTERN = /^instant(?:, click to remove)?$/i;
33
34
  const THINKING_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?thinking(?:, click to remove)?$/i;
34
35
  const PRO_CHIP_PATTERN = /^(?:(light|standard|extended|heavy)\s+)?pro(?:, click to remove)?$/i;
35
36
  const MODEL_FAMILY_CONTROL_KINDS = new Set(["button", "radio", "menuitemradio"]);
@@ -125,6 +126,12 @@ function parseComposerChipSelection(label) {
125
126
  };
126
127
  }
127
128
 
129
+ if (INSTANT_CHIP_PATTERN.test(normalized)) {
130
+ return {
131
+ modelFamily: /** @type {OracleUiModelFamily} */ ("instant"),
132
+ };
133
+ }
134
+
128
135
  const thinkingMatch = normalized.match(THINKING_CHIP_PATTERN);
129
136
  if (thinkingMatch) {
130
137
  return {
@@ -240,11 +247,20 @@ export function snapshotHasModelConfigurationUi(snapshot) {
240
247
  .filter((family) => matchesModelFamilyLabel(entry.label, family)),
241
248
  ),
242
249
  );
250
+ const visibleRadioFamilies = new Set(
251
+ entries
252
+ .filter((entry) => entry.kind === "radio" && typeof entry.label === "string")
253
+ .flatMap((entry) =>
254
+ /** @type {OracleUiModelFamily[]} */ (["instant", "thinking", "pro"])
255
+ .filter((family) => matchesModelFamilyLabel(entry.label, family)),
256
+ ),
257
+ );
243
258
  const hasCloseButton = entries.some((entry) => entry.kind === "button" && entry.label === "Close" && !entry.disabled);
259
+ const hasIntelligenceHeading = entries.some((entry) => entry.kind === "heading" && normalizeText(entry.label) === "Intelligence" && !entry.disabled);
244
260
  const hasEffortCombobox = entries.some(
245
261
  (entry) => entry.kind === "combobox" && EFFORT_LABELS.has(entry.value || "") && !entry.disabled,
246
262
  );
247
- return visibleFamilies.size >= 2 || hasCloseButton || hasEffortCombobox;
263
+ return visibleFamilies.size >= 2 || visibleRadioFamilies.size >= 2 || hasCloseButton || hasIntelligenceHeading || hasEffortCombobox;
248
264
  }
249
265
 
250
266
  /**
@@ -705,6 +705,14 @@ function matchesModelConfigurationOpener(candidate) {
705
705
  || /^(?:(?:Light|Standard|Extended|Heavy) )?Pro(?:, click to remove)?$/i.test(label);
706
706
  }
707
707
 
708
+ function canUseOpenModelMenuForSelection(snapshot, selection) {
709
+ if (selection.modelFamily !== "instant" || selection.autoSwitchToThinking === true) return false;
710
+ return Boolean(findEntry(
711
+ snapshot,
712
+ (candidate) => candidate.kind === "menuitemradio" && matchesModelFamilyControl(candidate, selection.modelFamily),
713
+ ));
714
+ }
715
+
708
716
  function composerControlsVisible(snapshot) {
709
717
  const entries = parseSnapshotEntries(snapshot);
710
718
  const hasComposer = entries.some(
@@ -802,25 +810,35 @@ function classifyChatPage({ job, url, snapshot, body, probe }) {
802
810
  const onAuthPath = typeof url === "string" && url.includes("/auth/");
803
811
  const hasUsableComposer = snapshotHasUsableComposerControls(snapshot);
804
812
 
805
- if (probe?.status === 401 || (probe?.status === 403 && (!onAllowedOrigin || !hasUsableComposer || probe?.domLoginCta))) {
813
+ const probeHasAccountIdentity = probe?.bodyHasId === true || probe?.bodyHasEmail === true;
814
+
815
+ if (probe?.status === 401 || (probe?.status === 403 && (!onAllowedOrigin || !hasUsableComposer))) {
806
816
  return { state: "login_required", message: "ChatGPT login is required. Run /oracle-auth." };
807
817
  }
808
818
 
809
819
  if (onAuthPath || probe?.onAuthPage) {
810
- if (probe?.bodyHasId || probe?.bodyHasEmail) {
820
+ if (probeHasAccountIdentity) {
811
821
  return {
812
822
  state: "auth_transitioning",
813
- message: "ChatGPT is on an auth page even though the backend session is partially authenticated. Rerun /oracle-auth.",
823
+ message: "ChatGPT is on an auth page even though the backend probe returned account-like fields. Rerun /oracle-auth.",
814
824
  };
815
825
  }
816
826
  return { state: "login_required", message: "ChatGPT login is required. Run /oracle-auth." };
817
827
  }
818
828
 
829
+ if (onAllowedOrigin && hasUsableComposer && probe?.domLoginCta && !probeHasAccountIdentity) {
830
+ return {
831
+ state: "login_required",
832
+ message: "ChatGPT login is required: the chat shell still shows public Log in/Sign up controls. Run /oracle-auth.",
833
+ };
834
+ }
835
+
819
836
  if (onAllowedOrigin && (probe?.status === 200 || probe?.status === 403) && hasUsableComposer) {
820
- if (probe?.domLoginCta && (probe?.bodyHasId || probe?.bodyHasEmail)) {
837
+ if (probe?.domLoginCta) {
838
+ // The public logged-out composer case returned above, so a remaining visible login CTA here still has account-like probe data.
821
839
  return {
822
840
  state: "auth_transitioning",
823
- message: "ChatGPT backend session is authenticated, but the web shell still shows public login CTA chrome. Rerun /oracle-auth.",
841
+ message: "ChatGPT backend probe returned account-like fields, but the web shell still shows public login controls. Rerun /oracle-auth.",
824
842
  };
825
843
  }
826
844
  return { state: "authenticated_and_ready", message: "ChatGPT is authenticated and ready." };
@@ -875,7 +893,7 @@ async function waitForOracleReady(job) {
875
893
  }
876
894
  if (elapsedMs >= 15_000) {
877
895
  await captureDiagnostics(job, "preflight-auth-transition");
878
- throw new Error("ChatGPT backend session is authenticated, but the web shell stayed in a partially logged-in state. Rerun /oracle-auth.");
896
+ throw new Error(classification.message || "ChatGPT auth did not settle into a ready chat shell. Rerun /oracle-auth.");
879
897
  }
880
898
  await sleep(1000);
881
899
  continue;
@@ -1009,6 +1027,7 @@ async function openModelConfiguration(job) {
1009
1027
  await agentBrowser(job, "wait", "800");
1010
1028
  const after = await snapshotText(job);
1011
1029
  if (snapshotHasModelConfigurationUi(after)) return after;
1030
+ if (canUseOpenModelMenuForSelection(after, job.selection)) return after;
1012
1031
 
1013
1032
  const configureEntry = findEntry(
1014
1033
  after,
@@ -1020,6 +1039,7 @@ async function openModelConfiguration(job) {
1020
1039
  await agentBrowser(job, "wait", "1200");
1021
1040
  const postConfigure = await snapshotText(job);
1022
1041
  if (snapshotHasModelConfigurationUi(postConfigure)) return postConfigure;
1042
+ if (canUseOpenModelMenuForSelection(postConfigure, job.selection)) return postConfigure;
1023
1043
  }
1024
1044
  }
1025
1045
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-oracle",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
4
4
  "description": "ChatGPT web-oracle extension for pi with isolated browser auth, async jobs, and project-context archives.",
5
5
  "private": false,
6
6
  "license": "MIT",