pi-oracle 0.6.15 → 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 +22 -0
- package/README.md +189 -121
- package/docs/ORACLE_DESIGN.md +1 -1
- package/extensions/oracle/index.ts +1 -1
- package/extensions/oracle/lib/commands.ts +3 -3
- package/extensions/oracle/lib/config.ts +1 -1
- package/extensions/oracle/lib/jobs.ts +1 -1
- package/extensions/oracle/lib/poller.ts +1 -1
- package/extensions/oracle/lib/tools.ts +3 -1
- package/extensions/oracle/shared/job-observability-helpers.d.mts +6 -0
- package/extensions/oracle/shared/job-observability-helpers.mjs +15 -0
- package/extensions/oracle/worker/auth-bootstrap.mjs +87 -7
- package/extensions/oracle/worker/auth-flow-helpers.mjs +17 -5
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +17 -1
- package/extensions/oracle/worker/run-job.mjs +26 -6
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.17 - 2026-05-10
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
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
|
|
17
|
+
|
|
18
|
+
## 0.6.16 - 2026-05-07
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- migrated the local pi development baseline and peer metadata from deprecated `@mariozechner/*` packages to maintained `@earendil-works/*` `0.74.0`
|
|
22
|
+
- regenerated the npm lockfile against the current stable dependency graph and refreshed the `basic-ftp` override to the current patched major
|
|
23
|
+
|
|
24
|
+
### Compatibility
|
|
25
|
+
- reviewed the pi `0.74.0` changelog and confirmed the oracle extension remains compatible with current extension lifecycle and package install/update guidance
|
|
26
|
+
|
|
5
27
|
## 0.6.15 - 2026-05-03
|
|
6
28
|
|
|
7
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,62 +1,118 @@
|
|
|
1
1
|
# pi-oracle
|
|
2
2
|
|
|
3
|
-
`pi-oracle`
|
|
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
|
-
|
|
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
|
-
|
|
7
|
+
## What a successful run looks like
|
|
13
8
|
|
|
14
|
-
|
|
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
|
|
43
|
+
|
|
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.
|
|
15
45
|
|
|
16
|
-
|
|
46
|
+
`pi-oracle` makes that workflow explicit and recoverable instead of asking the main agent to manually drive ChatGPT in your real browser window.
|
|
17
47
|
|
|
18
|
-
|
|
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
|
|
48
|
+
## What it does
|
|
23
49
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
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) |
|
|
28
57
|
|
|
29
|
-
##
|
|
58
|
+
## Fastest way to see it work
|
|
30
59
|
|
|
31
|
-
|
|
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
|
-
|
|
74
|
+
### 2. Check requirements
|
|
44
75
|
|
|
45
|
-
|
|
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`.
|
|
76
|
+
You need:
|
|
52
77
|
|
|
53
|
-
|
|
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.
|
|
93
|
+
|
|
94
|
+
### 4. Submit a tiny job
|
|
95
|
+
|
|
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
|
+
```
|
|
54
99
|
|
|
55
|
-
|
|
100
|
+
Expected result:
|
|
56
101
|
|
|
57
|
-
|
|
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.
|
|
58
109
|
|
|
59
|
-
If
|
|
110
|
+
If the wake-up does not arrive, run:
|
|
111
|
+
|
|
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
|
-
|
|
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["
|
|
87
|
-
C --> D["
|
|
88
|
-
D --> E["
|
|
89
|
-
E --> F["
|
|
90
|
-
F --> G["
|
|
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
|
-
|
|
148
|
+
Key design choices:
|
|
94
149
|
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
- `/oracle
|
|
100
|
-
- `/oracle-
|
|
101
|
-
- `/oracle-
|
|
102
|
-
- `/oracle-
|
|
103
|
-
- `/oracle-
|
|
104
|
-
- `/oracle-
|
|
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
|
-
##
|
|
176
|
+
## Configuration
|
|
114
177
|
|
|
115
|
-
Most users can start with
|
|
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,
|
|
134
|
-
-
|
|
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
|
-
|
|
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
|
|
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`
|
|
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
|
|
247
|
+
`oracle_submit` accepts canonical preset ids or a matching human-readable preset label. Keep config values on canonical ids.
|
|
185
248
|
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
257
|
+
## Privacy and local data
|
|
197
258
|
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
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,12 +280,24 @@ 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
|
|
287
|
+
|
|
288
|
+
This usually means the cookie import worked but the source cookies are not the active ChatGPT session you expected.
|
|
289
|
+
|
|
290
|
+
1. Open the configured browser profile.
|
|
291
|
+
2. Confirm ChatGPT works there without logging in again.
|
|
292
|
+
3. Quit the browser fully so its `Cookies` DB is stable.
|
|
293
|
+
4. Confirm `auth.chromeCookiePath` points at that exact profile's `Cookies` DB.
|
|
294
|
+
5. Confirm `auth.chromiumKeychain.services` names the browser's safe-storage Keychain service for that DB.
|
|
295
|
+
6. Re-run `/oracle-auth`.
|
|
296
|
+
|
|
297
|
+
### You hit a challenge or verification page
|
|
227
298
|
|
|
228
299
|
- Solve it in the auth/bootstrap browser if prompted.
|
|
229
|
-
-
|
|
300
|
+
- Re-run `/oracle-auth` before submitting jobs again.
|
|
230
301
|
|
|
231
302
|
### You see "Oracle requires a persisted pi session"
|
|
232
303
|
|
|
@@ -236,19 +307,19 @@ Project config should only override safe, non-privileged settings.
|
|
|
236
307
|
### A job finished but no wake-up arrived
|
|
237
308
|
|
|
238
309
|
- Use `/oracle-read [job-id]` to inspect the saved response preview.
|
|
239
|
-
- Use `/oracle-status
|
|
240
|
-
- Agent callers can use `oracle_read({ jobId })
|
|
310
|
+
- Use `/oracle-status` if you need help finding a recent job id.
|
|
311
|
+
- Agent callers can use `oracle_read({ jobId })`.
|
|
241
312
|
- Results are still saved on disk even if the reminder turn does not land.
|
|
242
313
|
|
|
243
314
|
### `/oracle-clean` refuses a terminal job right after completion
|
|
244
315
|
|
|
245
316
|
- This can happen during the short post-send retention grace window after a wake-up was sent.
|
|
246
|
-
- The command
|
|
247
|
-
- Wait until that time, then rerun `/oracle-clean
|
|
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>`.
|
|
248
319
|
|
|
249
320
|
### `agent-browser`, `tar`, or `zstd` is missing
|
|
250
321
|
|
|
251
|
-
|
|
322
|
+
Install the missing local dependency and rerun the command.
|
|
252
323
|
|
|
253
324
|
### Auto-detection picked the wrong browser profile
|
|
254
325
|
|
|
@@ -258,25 +329,11 @@ Project config should only override safe, non-privileged settings.
|
|
|
258
329
|
|
|
259
330
|
### You want more details about a failed run
|
|
260
331
|
|
|
261
|
-
|
|
262
|
-
- 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.
|
|
263
333
|
|
|
264
|
-
##
|
|
334
|
+
## Verification
|
|
265
335
|
|
|
266
|
-
|
|
267
|
-
- `docs/ORACLE_RECOVERY_DRILL.md` — safe expired-auth recovery validation drill
|
|
268
|
-
- `docs/ORACLE_ISOLATED_PI_VALIDATION.md` — repeatable isolated `pi` session smoke test for local-extension validation
|
|
269
|
-
|
|
270
|
-
## Privacy / local data
|
|
271
|
-
|
|
272
|
-
This extension is local-first, but it does read and persist local data:
|
|
273
|
-
- `/oracle-auth` reads ChatGPT cookies from the configured local browser profile
|
|
274
|
-
- job archives are uploaded to ChatGPT.com
|
|
275
|
-
- responses and artifacts are written under the configured oracle jobs dir
|
|
276
|
-
|
|
277
|
-
Review the code and design docs before using it with sensitive material.
|
|
278
|
-
|
|
279
|
-
## Validation helpers
|
|
336
|
+
Useful local checks:
|
|
280
337
|
|
|
281
338
|
```bash
|
|
282
339
|
npm run check:oracle-extension
|
|
@@ -284,21 +341,32 @@ npm run typecheck
|
|
|
284
341
|
npm run typecheck:worker-helpers
|
|
285
342
|
npm run sanity:oracle
|
|
286
343
|
npm run pack:check
|
|
287
|
-
# conventional local gate
|
|
288
344
|
npm test
|
|
289
|
-
# or all at once
|
|
290
345
|
npm run verify:oracle
|
|
291
346
|
```
|
|
292
347
|
|
|
293
|
-
`npm publish` is
|
|
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.
|
|
294
351
|
|
|
295
|
-
##
|
|
352
|
+
## Project map
|
|
296
353
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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).
|
|
302
370
|
|
|
303
371
|
## License
|
|
304
372
|
|
package/docs/ORACLE_DESIGN.md
CHANGED
|
@@ -73,7 +73,7 @@ The extension now follows the current `pi` session lifecycle model:
|
|
|
73
73
|
### Commands
|
|
74
74
|
|
|
75
75
|
- `/oracle-auth`
|
|
76
|
-
- syncs ChatGPT cookies from the
|
|
76
|
+
- syncs ChatGPT cookies from the configured local browser profile into the isolated oracle profile and verifies them there
|
|
77
77
|
- `/oracle-read [job-id]`
|
|
78
78
|
- shows job status plus the saved response preview
|
|
79
79
|
- `/oracle-status [job-id]`
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Invariants/Assumptions: Oracle only runs against persisted sessions, and startup maintenance should be best-effort without breaking session initialization.
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
8
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { loadOracleConfig } from "./lib/config.js";
|
|
10
10
|
import { registerOracleCommands } from "./lib/commands.js";
|
|
11
11
|
import { getSessionFile, pruneTerminalOracleJobs, reconcileStaleOracleJobs } from "./lib/jobs.js";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Invariants/Assumptions: Commands operate on persisted project-scoped jobs and rely on shared observability formatting for detached-state clarity.
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@
|
|
8
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { formatOracleCancelOutcome, formatOracleJobSummary } from "../shared/job-observability-helpers.mjs";
|
|
10
10
|
import { runOracleAuthBootstrap } from "./auth.js";
|
|
11
11
|
import {
|
|
@@ -67,9 +67,9 @@ function readScopedJob(jobId: string, cwd: string) {
|
|
|
67
67
|
|
|
68
68
|
export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string, workerPath: string): void {
|
|
69
69
|
pi.registerCommand("oracle-auth", {
|
|
70
|
-
description: "Sync ChatGPT cookies from
|
|
70
|
+
description: "Sync ChatGPT cookies from the configured local browser profile into the oracle auth seed profile",
|
|
71
71
|
handler: async (_args, ctx) => {
|
|
72
|
-
ctx.ui.notify("Syncing ChatGPT cookies from
|
|
72
|
+
ctx.ui.notify("Syncing ChatGPT cookies from the configured local browser profile into the oracle auth seed profile…", "info");
|
|
73
73
|
try {
|
|
74
74
|
const result = await runOracleAuthBootstrap(authWorkerPath, ctx.cwd);
|
|
75
75
|
ctx.ui.notify(result, "info");
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import { getAgentDir } from "@
|
|
9
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { isAbsolute, join, normalize } from "node:path";
|
|
11
11
|
import { getProjectId } from "./runtime.js";
|
|
12
12
|
|
|
@@ -7,7 +7,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
8
8
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { isAbsolute, join, relative as relativePath, resolve, sep } from "node:path";
|
|
10
|
-
import type { ExtensionContext } from "@
|
|
10
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import {
|
|
12
12
|
ACTIVE_ORACLE_JOB_STATUSES,
|
|
13
13
|
applyOracleJobCleanupWarnings,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Usage: Imported by the oracle extension entrypoint to start or stop per-session oracle polling.
|
|
5
5
|
// Invariants/Assumptions: Poller scans are serialized per session key, wake-up delivery is best-effort, and terminal-job notifications always re-read durable job state before send.
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { buildOracleStatusText, buildOracleWakeupNotificationContent } from "../shared/job-observability-helpers.mjs";
|
|
9
9
|
import { isProcessAlive, readProcessStartedAt } from "../shared/process-helpers.mjs";
|
|
10
10
|
import { isLockTimeoutError, listLeaseMetadata, releaseLease, withGlobalReconcileLock, writeLeaseMetadata } from "./locks.js";
|
|
@@ -8,7 +8,7 @@ import { lstat, mkdtemp, readdir, rename, rm, stat, writeFile } from "node:fs/pr
|
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { basename, join, posix } from "node:path";
|
|
10
10
|
import { runOracleAuthBootstrap } from "./auth.js";
|
|
11
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
11
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { Type } from "typebox";
|
|
13
13
|
import { formatOracleCancelOutcome, formatOracleJobSummary, formatOracleSubmitResponse } from "../shared/job-observability-helpers.mjs";
|
|
14
14
|
import { getLatestOracleJobLifecycleEvent, getLatestOracleTerminalLifecycleEvent, transitionOracleJobPhase } from "../shared/job-lifecycle-helpers.mjs";
|
|
@@ -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),
|
|
@@ -463,13 +463,97 @@ function cookieSource() {
|
|
|
463
463
|
return config.auth.chromeCookiePath || config.auth.chromeProfile;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
function usesConfiguredChromiumCookieSource() {
|
|
467
|
+
return Boolean(config.auth.chromeCookiePath && config.auth.chromiumKeychain);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function browserSourceName() {
|
|
471
|
+
if (config.browser.executablePath) return basename(config.browser.executablePath);
|
|
472
|
+
if (usesConfiguredChromiumCookieSource()) return "configured Chromium browser";
|
|
473
|
+
return "Google Chrome";
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function keychainSummary() {
|
|
477
|
+
const keychain = config.auth.chromiumKeychain;
|
|
478
|
+
if (!keychain) return undefined;
|
|
479
|
+
const services = Array.isArray(keychain.services) && keychain.services.length > 0 ? keychain.services.join(", ") : "(none configured)";
|
|
480
|
+
const label = keychain.label || services;
|
|
481
|
+
return `${label} (account: ${keychain.account}, services: ${services})`;
|
|
482
|
+
}
|
|
483
|
+
|
|
466
484
|
function cookieSourceLabel() {
|
|
467
|
-
if (
|
|
485
|
+
if (usesConfiguredChromiumCookieSource()) return `configured Chromium cookie DB ${config.auth.chromeCookiePath}`;
|
|
468
486
|
return config.auth.chromeCookiePath
|
|
469
487
|
? `Chrome cookie DB ${config.auth.chromeCookiePath}`
|
|
470
488
|
: `Chrome profile ${config.auth.chromeProfile}`;
|
|
471
489
|
}
|
|
472
490
|
|
|
491
|
+
function formatAuthSuccessMessage({ classificationMessage, appliedCount, targetDir }) {
|
|
492
|
+
const lines = [
|
|
493
|
+
"Oracle auth synced.",
|
|
494
|
+
"",
|
|
495
|
+
"Source:",
|
|
496
|
+
`- Browser: ${browserSourceName()}`,
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
if (config.auth.chromeCookiePath) lines.push(`- Cookie DB: ${config.auth.chromeCookiePath}`);
|
|
500
|
+
else lines.push(`- Profile: ${config.auth.chromeProfile}`);
|
|
501
|
+
|
|
502
|
+
const keychain = keychainSummary();
|
|
503
|
+
if (keychain) lines.push(`- Keychain: ${keychain}`);
|
|
504
|
+
lines.push(`- Cookies synced: ${appliedCount}`);
|
|
505
|
+
lines.push("", "Auth seed profile:", targetDir, "", "Diagnostics:", DIAGNOSTICS_DIR, "", "Status:", classificationMessage);
|
|
506
|
+
return lines.join("\n");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function formatAuthFailureGuidance(error) {
|
|
510
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
511
|
+
const lines = ["Oracle auth failed.", "", `Reason: ${reason}`, "", "Likely causes:"];
|
|
512
|
+
|
|
513
|
+
if (usesConfiguredChromiumCookieSource()) {
|
|
514
|
+
lines.push(
|
|
515
|
+
"- the configured cookie DB is stale or from the wrong browser profile",
|
|
516
|
+
"- ChatGPT is logged out in that browser profile",
|
|
517
|
+
"- auth.chromiumKeychain does not match the browser safe-storage item for that cookie DB",
|
|
518
|
+
"- the target browser was still running while /oracle-auth read its Cookies DB",
|
|
519
|
+
"",
|
|
520
|
+
"Next:",
|
|
521
|
+
"1. Open the configured browser profile.",
|
|
522
|
+
"2. Confirm ChatGPT works there.",
|
|
523
|
+
"3. Quit the browser fully.",
|
|
524
|
+
"4. Confirm auth.chromeCookiePath points at that profile's Cookies DB.",
|
|
525
|
+
"5. Confirm auth.chromiumKeychain.services names the browser's safe-storage Keychain service.",
|
|
526
|
+
"6. Re-run /oracle-auth.",
|
|
527
|
+
);
|
|
528
|
+
} else {
|
|
529
|
+
lines.push(
|
|
530
|
+
"- ChatGPT is logged out in the configured local browser profile",
|
|
531
|
+
"- auth.chromeProfile or auth.chromeCookiePath points at the wrong profile",
|
|
532
|
+
"- the profile cookie store is stale or unreadable",
|
|
533
|
+
"",
|
|
534
|
+
"Next:",
|
|
535
|
+
"1. Open the configured local browser profile.",
|
|
536
|
+
"2. Confirm ChatGPT works there.",
|
|
537
|
+
"3. Quit the browser fully.",
|
|
538
|
+
"4. Re-run /oracle-auth.",
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
lines.push(
|
|
543
|
+
"",
|
|
544
|
+
"Details:",
|
|
545
|
+
`- Source: ${cookieSourceLabel()}`,
|
|
546
|
+
`- Browser: ${browserSourceName()}`,
|
|
547
|
+
`- Auth seed profile: ${config.browser.authSeedProfileDir}`,
|
|
548
|
+
`- Diagnostics: ${DIAGNOSTICS_DIR || "(oracle-auth diagnostics dir unavailable)"}`,
|
|
549
|
+
`- Log: ${LOG_PATH}`,
|
|
550
|
+
"",
|
|
551
|
+
authConfigSummary(),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
return lines.join("\n");
|
|
555
|
+
}
|
|
556
|
+
|
|
473
557
|
async function readRawSourceCookies() {
|
|
474
558
|
if (config.auth.chromeCookiePath && config.auth.chromiumKeychain) {
|
|
475
559
|
return await getCookiesFromConfiguredChromiumSource({
|
|
@@ -825,9 +909,7 @@ async function run() {
|
|
|
825
909
|
const generation = new Date().toISOString();
|
|
826
910
|
await writeFile(join(profilePlan.targetDir, ".oracle-seed-generation"), `${generation}\n`, { encoding: "utf8", mode: 0o600 });
|
|
827
911
|
committedProfile = true;
|
|
828
|
-
process.stdout.write(
|
|
829
|
-
`${classification.message} Synced ${appliedCount} cookies into ${profilePlan.targetDir}. Diagnostics: ${DIAGNOSTICS_DIR}`,
|
|
830
|
-
);
|
|
912
|
+
process.stdout.write(formatAuthSuccessMessage({ classificationMessage: classification.message, appliedCount, targetDir: profilePlan.targetDir }));
|
|
831
913
|
} catch (error) {
|
|
832
914
|
shouldPreserveBrowser = Boolean(error && typeof error === "object" && error.preserveBrowser === true);
|
|
833
915
|
await log(`Auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -843,8 +925,6 @@ async function run() {
|
|
|
843
925
|
}
|
|
844
926
|
|
|
845
927
|
run().catch((error) => {
|
|
846
|
-
process.stderr.write(
|
|
847
|
-
`${error instanceof Error ? error.message : String(error)}\nSee ${LOG_PATH} and diagnostics in ${DIAGNOSTICS_DIR || "(oracle-auth diagnostics dir unavailable)"}\n${authConfigSummary()}\nIf needed, ensure the configured browser profile is already logged into ChatGPT and grant macOS Keychain access when prompted.`,
|
|
848
|
-
);
|
|
928
|
+
process.stderr.write(formatAuthFailureGuidance(error));
|
|
849
929
|
process.exit(1);
|
|
850
930
|
});
|
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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 (
|
|
820
|
+
if (probeHasAccountIdentity) {
|
|
811
821
|
return {
|
|
812
822
|
state: "auth_transitioning",
|
|
813
|
-
message: "ChatGPT is on an auth page even though the backend
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"check:oracle-extension": "node --check extensions/oracle/shared/process-helpers.mjs && node --check extensions/oracle/shared/state-coordination-helpers.mjs && node --check extensions/oracle/shared/job-coordination-helpers.mjs && node --check extensions/oracle/shared/job-lifecycle-helpers.mjs && node --check extensions/oracle/shared/job-observability-helpers.mjs && node --check extensions/oracle/worker/run-job.mjs && node --check extensions/oracle/worker/state-locks.mjs && node --check extensions/oracle/worker/artifact-heuristics.mjs && node --check extensions/oracle/worker/chatgpt-ui-helpers.mjs && node --check extensions/oracle/worker/chatgpt-flow-helpers.mjs && node --check extensions/oracle/worker/auth-flow-helpers.mjs && node --check extensions/oracle/worker/auth-cookie-policy.mjs && node --check extensions/oracle/worker/chromium-cookie-source.mjs && node --check extensions/oracle/worker/auth-bootstrap.mjs && esbuild extensions/oracle/index.ts --bundle --platform=node --format=esm --external:@
|
|
45
|
+
"check:oracle-extension": "node --check extensions/oracle/shared/process-helpers.mjs && node --check extensions/oracle/shared/state-coordination-helpers.mjs && node --check extensions/oracle/shared/job-coordination-helpers.mjs && node --check extensions/oracle/shared/job-lifecycle-helpers.mjs && node --check extensions/oracle/shared/job-observability-helpers.mjs && node --check extensions/oracle/worker/run-job.mjs && node --check extensions/oracle/worker/state-locks.mjs && node --check extensions/oracle/worker/artifact-heuristics.mjs && node --check extensions/oracle/worker/chatgpt-ui-helpers.mjs && node --check extensions/oracle/worker/chatgpt-flow-helpers.mjs && node --check extensions/oracle/worker/auth-flow-helpers.mjs && node --check extensions/oracle/worker/auth-cookie-policy.mjs && node --check extensions/oracle/worker/chromium-cookie-source.mjs && node --check extensions/oracle/worker/auth-bootstrap.mjs && esbuild extensions/oracle/index.ts --bundle --platform=node --format=esm --external:@earendil-works/pi-coding-agent --external:@earendil-works/pi-ai --external:typebox --outfile=/tmp/pi-oracle-extension-check.js",
|
|
46
46
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
47
47
|
"typecheck:worker-helpers": "tsc --noEmit -p tsconfig.worker-helpers.json",
|
|
48
48
|
"sanity:oracle": "node scripts/oracle-sanity-runner.mjs",
|
|
@@ -55,19 +55,19 @@
|
|
|
55
55
|
"@steipete/sweet-cookie": "^0.2.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"
|
|
59
|
-
"
|
|
58
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
59
|
+
"typebox": "*"
|
|
60
60
|
},
|
|
61
61
|
"overrides": {
|
|
62
|
-
"basic-ftp": "
|
|
62
|
+
"basic-ftp": "6.0.1",
|
|
63
63
|
"protobufjs": "7.5.5"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@
|
|
67
|
-
"@types/node": "^25.6.
|
|
66
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
67
|
+
"@types/node": "^25.6.1",
|
|
68
68
|
"esbuild": "^0.28.0",
|
|
69
69
|
"tsx": "^4.21.0",
|
|
70
|
-
"typebox": "^1.1.
|
|
70
|
+
"typebox": "^1.1.38",
|
|
71
71
|
"typescript": "^6.0.3"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
"os": [
|
|
77
77
|
"darwin"
|
|
78
78
|
],
|
|
79
|
-
"packageManager": "npm@
|
|
79
|
+
"packageManager": "npm@11.14.0"
|
|
80
80
|
}
|