pi-oracle 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +6 -6
- package/docs/ORACLE_DESIGN.md +13 -9
- package/docs/ORACLE_ISOLATED_PI_VALIDATION.md +18 -17
- package/docs/platform-smoke.md +1 -1
- package/extensions/oracle/index.ts +84 -4
- package/extensions/oracle/lib/auth.ts +4 -4
- package/extensions/oracle/lib/commands.ts +48 -22
- package/extensions/oracle/lib/poller.ts +20 -5
- package/extensions/oracle/lib/runtime.ts +8 -0
- package/extensions/oracle/lib/tools.ts +18 -5
- package/extensions/oracle/shared/browser-profile-helpers.d.mts +15 -0
- package/extensions/oracle/shared/browser-profile-helpers.mjs +37 -13
- package/extensions/oracle/shared/job-observability-helpers.d.mts +3 -1
- package/extensions/oracle/shared/job-observability-helpers.mjs +14 -5
- package/extensions/oracle/worker/chatgpt-flow-helpers.d.mts +9 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.mjs +29 -2
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +1 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +52 -13
- package/extensions/oracle/worker/run-job.mjs +179 -53
- package/package.json +3 -6
- package/prompts/oracle-followup.md +2 -2
- package/prompts/oracle.md +13 -5
- package/scripts/oracle-real-smoke.mjs +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.8 - 2026-06-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-ai` `0.79.1` and regenerated the npm lockfile
|
|
9
|
+
- documented `pi` `0.79.1+` as the suggested tested floor while keeping pi runtime packages as optional wildcard peers so npm peer ranges do not block users from trying newer pi releases
|
|
10
|
+
- updated isolated local-extension and packed package validation workflows to pass explicit `--approve` when they intentionally trust their temporary project fixtures under Pi 0.79.1 project-trust rules
|
|
11
|
+
- made TUI `/oracle` and `/oracle-followup` commands reappear as compact user messages for prompt-history/up-arrow recall while keeping verbose dispatch instructions hidden
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- made project-local `.pi/extensions/oracle.json` overrides honor Pi's effective project trust decision (`ctx.isProjectTrusted()`), including `--no-approve` and saved “do not trust” decisions while preserving the historical default of loading safe project overrides for existing oracle users
|
|
15
|
+
- updated ChatGPT model-selection handling for the current compact selector labels, including `Extra High`, `Pro Standard`, and `Pro Extended`
|
|
16
|
+
- hardened ChatGPT/Grok send handling so oracle workers require provider acceptance evidence before entering `awaiting_response`, preventing unsent composer drafts from masquerading as running jobs
|
|
17
|
+
- dismissed ChatGPT Pro feedback dialogs during model configuration instead of mistaking their generic `Close` control for configuration UI
|
|
18
|
+
|
|
19
|
+
### Compatibility
|
|
20
|
+
- reviewed the pi `0.79.1` changelog, project-trust docs, extension docs, package docs, prompt-template docs, SDK/RPC exports, and matching examples; the oracle extension remains compatible with current extension lifecycle and package install/update behavior
|
|
21
|
+
|
|
5
22
|
## 0.7.7 - 2026-06-08
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`pi-oracle` lets a `pi` agent send hard, long-running work to ChatGPT.com or Grok 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
|
-
> Status: experimental public beta. Validated on macOS, Linux, and Windows native with Chromium-family browsers and pi `0.79.
|
|
5
|
+
> Status: experimental public beta. Validated on macOS, Linux, and Windows native with Chromium-family browsers and pi `0.79.1`. Pi `0.79.1+` is the suggested tested floor for project-trust-aware package/runtime validation, but pi-bundled runtime packages remain optional wildcard peers so npm peer ranges do not block users from trying newer pi releases. Normal oracle jobs run in an isolated browser profile, not your active browser window.
|
|
6
6
|
|
|
7
7
|
## What a successful run looks like
|
|
8
8
|
|
|
@@ -77,7 +77,7 @@ You need:
|
|
|
77
77
|
|
|
78
78
|
- macOS, Linux, or Windows native
|
|
79
79
|
- Node.js 22 or newer
|
|
80
|
-
- Suggested tested floor: `pi` 0.79.
|
|
80
|
+
- Suggested tested floor: `pi` 0.79.1 or newer; older pi versions are not blocked by package metadata but are outside the current validation baseline
|
|
81
81
|
- Google Chrome/Chromium or another Chromium-family browser
|
|
82
82
|
- ChatGPT or Grok already signed in to the configured local browser profile for the provider you plan to use
|
|
83
83
|
- `agent-browser`, `tar`, and `zstd` available on the machine
|
|
@@ -149,7 +149,7 @@ flowchart LR
|
|
|
149
149
|
|
|
150
150
|
Key design choices:
|
|
151
151
|
|
|
152
|
-
- **
|
|
152
|
+
- **Extension-managed dispatch owns context gathering.** In the TUI, `/oracle` and `/oracle-followup` are intercepted before prompt-template expansion, re-added as compact user messages for prompt-history/up-arrow recall, and paired with detailed dispatch instructions as hidden context. The visible transcript stays compact while the agent still preflights, gathers context, chooses archive inputs, and stops after dispatch.
|
|
153
153
|
- **Tools own execution.** `oracle_submit` builds the archive, admits or queues the job, starts the worker, and returns immediately.
|
|
154
154
|
- **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.
|
|
155
155
|
- **Follow-ups preserve provider thread state.** `/oracle-followup <job-id> ...` resolves the prior job's saved provider URL and submits the next prompt with `followUpJobId`.
|
|
@@ -179,7 +179,7 @@ Agent-facing tools:
|
|
|
179
179
|
|
|
180
180
|
Most users can start with defaults. Set an agent-level config only when you need a non-default provider, mode, preset, or browser profile.
|
|
181
181
|
|
|
182
|
-
Pi 0.79.
|
|
182
|
+
Pi 0.79.1 gates project-local inputs behind project trust. `pi-oracle` preserves its historical risk-on extension behavior for existing users: project-local `.pi/extensions/oracle.json` safe overrides still load by default for compatibility. They are ignored when you explicitly opt out of project-local inputs with `--no-approve` or save a “do not trust” decision for the project. Privileged browser/auth settings still come only from the agent-level config.
|
|
183
183
|
|
|
184
184
|
`~/.pi/agent/extensions/oracle.json`
|
|
185
185
|
|
|
@@ -406,8 +406,8 @@ For manual end-to-end local-extension smoke testing, use [`docs/ORACLE_ISOLATED_
|
|
|
406
406
|
| `extensions/oracle/lib/` | Commands, tools, config, jobs, queueing, runtime, poller |
|
|
407
407
|
| `extensions/oracle/worker/` | Detached provider web worker and UI/auth helpers |
|
|
408
408
|
| `extensions/oracle/shared/` | Shared process, state, job, and observability helpers |
|
|
409
|
-
| [`prompts/oracle.md`](prompts/oracle.md) | `/oracle`
|
|
410
|
-
| [`prompts/oracle-followup.md`](prompts/oracle-followup.md) | `/oracle-followup`
|
|
409
|
+
| [`prompts/oracle.md`](prompts/oracle.md) | Hidden `/oracle` command-dispatch workflow |
|
|
410
|
+
| [`prompts/oracle-followup.md`](prompts/oracle-followup.md) | Hidden `/oracle-followup` command-dispatch workflow |
|
|
411
411
|
| `scripts/oracle-sanity-*` | Local sanity and archive-safety checks |
|
|
412
412
|
| `scripts/platform-smoke*` | Crabbox macOS, Ubuntu, and Windows release smoke gate |
|
|
413
413
|
| [`docs/ORACLE_DESIGN.md`](docs/ORACLE_DESIGN.md) | Architecture, lifecycle, queueing, persistence, recovery behavior |
|
package/docs/ORACLE_DESIGN.md
CHANGED
|
@@ -7,7 +7,7 @@ Companion doc:
|
|
|
7
7
|
- `docs/ORACLE_RECOVERY_DRILL.md` — safe expired-auth recovery validation drill
|
|
8
8
|
|
|
9
9
|
Compatibility target:
|
|
10
|
-
- `pi` 0.79.
|
|
10
|
+
- `pi` 0.79.1+ is the suggested tested floor for current project-trust-aware package/runtime validation
|
|
11
11
|
- package metadata keeps pi runtime packages as optional wildcard peers, so this suggested floor is not enforced as a hard npm install requirement
|
|
12
12
|
- current extension lifecycle only; no backward-compatibility shims for removed `session_switch` / `session_fork` events
|
|
13
13
|
|
|
@@ -60,14 +60,15 @@ The extension now follows the current `pi` session lifecycle model:
|
|
|
60
60
|
- previous runtimes are expected to clean up in `session_shutdown`
|
|
61
61
|
- no new logic depends on removed post-transition events
|
|
62
62
|
|
|
63
|
-
###
|
|
63
|
+
### Oracle dispatch commands
|
|
64
64
|
|
|
65
65
|
- `/oracle <request>`
|
|
66
|
-
-
|
|
66
|
+
- in TUI mode, intercepted by the extension before prompt-template expansion so verbose internal workflow rules stay hidden from the visible transcript
|
|
67
|
+
- injects the detailed dispatch instructions as a hidden custom message
|
|
68
|
+
- in print/json/rpc modes, the extension contributes the prompt templates so non-interactive prompt expansion still works
|
|
67
69
|
- asks the agent to gather context and dispatch an oracle job
|
|
68
|
-
- intentionally uses native pi prompt/template queueing so submissions survive streaming and compaction
|
|
69
70
|
- `/oracle-followup <job-id> <request>`
|
|
70
|
-
-
|
|
71
|
+
- follows the same hidden-instructions TUI path and print/json prompt-template fallback
|
|
71
72
|
- asks the agent to continue an earlier oracle job in the same provider thread via `followUpJobId`
|
|
72
73
|
- keeps same-thread continuation available to normal users without requiring raw tool-call syntax
|
|
73
74
|
|
|
@@ -106,14 +107,14 @@ The extension now follows the current `pi` session lifecycle model:
|
|
|
106
107
|
### `/oracle ...`
|
|
107
108
|
|
|
108
109
|
`/oracle <request>` should not directly drive ChatGPT or Grok.
|
|
109
|
-
|
|
110
|
+
In TUI mode, the extension intercepts it before prompt-template expansion, re-injects the compact slash request as the visible user message so prompt-history/up-arrow recall survives session reloads, injects hidden dispatch instructions before the agent starts, and shows only compact user-facing status. In print/json/rpc modes, the extension exposes the prompt template so one-shot `/oracle` still expands and runs normally.
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
It instructs the agent to:
|
|
112
113
|
|
|
113
114
|
1. call `oracle_preflight` immediately, passing `provider: "grok"` when the user explicitly asks for Grok
|
|
114
115
|
2. stop right away if preflight reports the session or local oracle setup is not ready
|
|
115
116
|
3. understand whether the request is explicitly narrow or genuinely broad
|
|
116
|
-
4. if
|
|
117
|
+
4. if auth is missing, stale, or the worker explicitly said to rerun `/oracle-auth`, stop and tell the user to run `/oracle-auth` rather than launching auth automatically
|
|
117
118
|
5. gather enough repo context to submit well and bias toward context-rich archives when they fit within the provider ceiling: 250 MB for ChatGPT and 200 MiB for Grok
|
|
118
119
|
6. if the request is narrow, start from the directly relevant area but still include nearby tests, docs, config, and adjacent modules when they may improve answer quality
|
|
119
120
|
7. if the request is broad/repo-wide, gather broader context and usually archive `.`
|
|
@@ -229,7 +230,7 @@ Merged config locations:
|
|
|
229
230
|
- global: `~/.pi/agent/extensions/oracle.json`
|
|
230
231
|
- project: `.pi/extensions/oracle.json`
|
|
231
232
|
|
|
232
|
-
Project config remains restricted to safe overrides only. On Pi 0.79.
|
|
233
|
+
Project config remains restricted to safe overrides only. On Pi 0.79.1+, pi itself gates project-local inputs behind project trust, but `pi-oracle` keeps its historical risk-on extension behavior for this package-specific safe override file: `.pi/extensions/oracle.json` loads by default for compatibility, and is ignored when Pi reports the project is untrusted, including `--no-approve` or saved “do not trust” decisions. This preserves the existing extension experience while still honoring explicit opt-out/distrust decisions. Browser/auth settings remain global-only because they control local privileged browser state.
|
|
233
234
|
|
|
234
235
|
### Current config shape
|
|
235
236
|
|
|
@@ -631,6 +632,9 @@ Remaining non-blocking hardening work:
|
|
|
631
632
|
- keep hardening model-selection verification against future ChatGPT UI variation
|
|
632
633
|
|
|
633
634
|
Recent proof points:
|
|
635
|
+
- Pi 0.79.1 release gate: `npm run release:check` passed on 2026-06-11 after the project-trust, prompt-history, ChatGPT selector, and send-acceptance updates, including `verify:oracle` plus Crabbox macOS, Ubuntu, and Windows native `platform-build` and `real-extension` suites
|
|
636
|
+
- Pi 0.79.1 platform artifacts: `.artifacts/platform-smoke/run-1781196218405-311wzs` (macOS platform-build), `.artifacts/platform-smoke/run-1781196261807-eb0391` (macOS real-extension), `.artifacts/platform-smoke/run-1781196230636-ze1hai` (Ubuntu platform-build), `.artifacts/platform-smoke/run-1781196265638-kxiwh9` (Ubuntu real-extension), `.artifacts/platform-smoke/run-1781196255488-ucuf35` (Windows native platform-build), `.artifacts/platform-smoke/run-1781196369098-4qlzjs` (Windows native real-extension)
|
|
637
|
+
- Pi 0.79.1 live source-extension send-acceptance smoke: new-chat job `4b98776f-d422-4bfb-8a6a-7aef73c31bf6` reached `https://chatgpt.com/c/6a2ac99d-fc5c-83e8-88d7-5e1e8f427499` and completed; same-thread follow-up job `abb4f590-96a1-4aab-b91a-c0a7cc15a162` completed on the unchanged conversation URL after send-acceptance evidence
|
|
634
638
|
- Pi 0.79.0 release gate: `npm run release:check` passed on 2026-06-08, including `verify:oracle` plus Crabbox macOS, Ubuntu, and Windows native `platform-build` and `real-extension` suites
|
|
635
639
|
- Pi 0.79.0 platform artifacts: `.artifacts/platform-smoke/run-1780938522145-50q2f2` (macOS platform-build), `.artifacts/platform-smoke/run-1780938572090-bi87g5` (macOS real-extension), `.artifacts/platform-smoke/run-1780938542847-quridb` (Ubuntu platform-build), `.artifacts/platform-smoke/run-1780938587248-c8uo4c` (Ubuntu real-extension), `.artifacts/platform-smoke/run-1780938585007-l0xapp` (Windows native platform-build), `.artifacts/platform-smoke/run-1780938820527-c1j8tt` (Windows native real-extension)
|
|
636
640
|
- Pi 0.79.0 isolated local-extension model-agent smoke: `.artifacts/real-smoke/run-1780935835596-pfbn5o` passed with `PI_ORACLE_REAL_TEST_MODEL_AGENT=1 npm run smoke:real:source`
|
|
@@ -27,18 +27,13 @@ The extension is loaded from the local checkout with:
|
|
|
27
27
|
pi --approve --no-extensions -e "$REPO/extensions/oracle/index.ts"
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
That ensures the session is exercising the in-repo code, not a globally installed package. `--approve` is intentional for this isolated workflow on Pi 0.79.
|
|
30
|
+
That ensures the session is exercising the in-repo code, not a globally installed package. `--approve` is intentional for this isolated workflow on Pi 0.79.1+: the test fixture is this trusted checkout, and non-interactive/scripted validation must not block on the project-trust prompt.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The local extension now intercepts TUI `/oracle` and `/oracle-followup` before prompt-template expansion, re-injects the compact slash request as the visible user message for prompt-history/up-arrow recall, and reads the in-repo prompt files as hidden dispatch instructions, so do not pass `--prompt-template` for normal local-extension validation. In print/json/rpc modes, the extension contributes the prompt templates itself.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
pi --approve --no-extensions -e "$REPO/extensions/oracle/index.ts" \
|
|
36
|
-
--no-prompt-templates --prompt-template "$REPO/prompts/oracle.md"
|
|
37
|
-
```
|
|
34
|
+
Do not add `https://github.com/fitchmultz/pi-oracle` to this repository's `.pi/settings.json` just to test local oracle changes. If you already keep `npm:pi-oracle` installed globally, mixing the global npm package with a project-local git package creates two distinct package identities and can trigger prompt/tool conflicts. Use the explicit CLI extension flag above instead.
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
`oracle_submit` now preflights a missing or unreadable auth seed profile before it creates an archive or persists a job. For archive-inspection smoke tests that intentionally run without real auth, create an empty isolated seed-profile directory under the temporary agent dir so submission can proceed far enough to write the archive while still staying isolated from your normal Chrome state.
|
|
36
|
+
`oracle_submit` now preflights missing, unreadable, or unverified auth seed profiles before it creates an archive or persists a job. For archive-inspection smoke tests that intentionally run without real auth, use `oracle_preflight` for the blocker path or create a test seed only in a purpose-built fixture that includes the `.oracle-seed-generation` marker.
|
|
42
37
|
|
|
43
38
|
## Preset requirement
|
|
44
39
|
|
|
@@ -81,7 +76,12 @@ mkdir -p \
|
|
|
81
76
|
"$TEST2_AGENT" "$TEST2_SESSIONS" "$TEST2_JOBS" \
|
|
82
77
|
"$FIXTURE" "$OUTSIDE"
|
|
83
78
|
|
|
84
|
-
mkdir -p
|
|
79
|
+
mkdir -p \
|
|
80
|
+
"$TEST1_AGENT/extensions/oracle-auth-seed-profile" \
|
|
81
|
+
"$TEST2_AGENT/extensions/oracle-auth-seed-profile"
|
|
82
|
+
touch \
|
|
83
|
+
"$TEST1_AGENT/extensions/oracle-auth-seed-profile/.oracle-seed-generation" \
|
|
84
|
+
"$TEST2_AGENT/extensions/oracle-auth-seed-profile/.oracle-seed-generation"
|
|
85
85
|
|
|
86
86
|
echo 'secret' > "$OUTSIDE/secret.txt"
|
|
87
87
|
ln -s "$OUTSIDE" "$FIXTURE/linked-outside"
|
|
@@ -162,29 +162,30 @@ Expected behavior:
|
|
|
162
162
|
Notes:
|
|
163
163
|
|
|
164
164
|
- this smoke test does not require `/oracle-auth`
|
|
165
|
-
- the snippet creates an
|
|
166
|
-
- with that
|
|
165
|
+
- the snippet creates an isolated test auth seed profile plus `.oracle-seed-generation` marker for `TEST1_AGENT` because `oracle_submit` now rejects missing or unverified seed profiles before archiving
|
|
166
|
+
- with that marker-only seed profile, the worker still fails later due to missing real auth, which is useful because the archive remains on disk for inspection
|
|
167
167
|
|
|
168
168
|
### Test 2: symlink escape rejection
|
|
169
169
|
|
|
170
170
|
Expected behavior:
|
|
171
171
|
|
|
172
172
|
- `oracle_submit` rejects `linked-outside/secret.txt`
|
|
173
|
+
- the snippet creates the same marker-only isolated auth seed profile for `TEST2_AGENT` so the test reaches archive input validation
|
|
173
174
|
- the error should say the archive input must resolve inside the project cwd without symlink escapes
|
|
174
175
|
- no oracle job directory should be created for the rejected submit
|
|
175
176
|
|
|
176
|
-
## Testing local `/oracle` prompt changes too
|
|
177
|
+
## Testing local `/oracle` command-prompt changes too
|
|
177
178
|
|
|
178
|
-
The main smoke test above calls `oracle_submit` directly, so it only needs the local extension entrypoint. If you also changed `prompts/oracle.md`, start the isolated session with the local prompt
|
|
179
|
+
The main smoke test above calls `oracle_submit` directly, so it only needs the local extension entrypoint. If you also changed `prompts/oracle.md`, start the isolated session with the same local extension entrypoint; the extension reads the in-repo prompt file as hidden command-dispatch instructions:
|
|
179
180
|
|
|
180
181
|
```bash
|
|
181
|
-
LOCAL_ORACLE_PI_CMD="pi --approve --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'
|
|
182
|
+
LOCAL_ORACLE_PI_CMD="pi --approve --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
|
|
182
183
|
TMUX_CMD1="cd '$REPO' && env PI_CODING_AGENT_DIR='$TEST1_AGENT' PI_ORACLE_JOBS_DIR='$TEST1_JOBS' PATH='$PATH' $LOCAL_ORACLE_PI_CMD"
|
|
183
184
|
```
|
|
184
185
|
|
|
185
|
-
Use the same pattern for additional sessions, swapping the session/job directories as needed. This keeps the test on the in-repo extension and in-repo prompt
|
|
186
|
+
Use the same pattern for additional sessions, swapping the session/job directories as needed. This keeps the test on the in-repo extension and hidden in-repo command prompt without depending on `.pi/settings.json` package entries.
|
|
186
187
|
|
|
187
|
-
`/oracle` now starts by calling `oracle_preflight`. If you want the
|
|
188
|
+
`/oracle` now starts by calling `oracle_preflight`. If you want the command flow to proceed past that early guard in an isolated test without using your normal auth state, run `/oracle-auth` in the isolated agent dir or create a purpose-built verified seed fixture with `.oracle-seed-generation`.
|
|
188
189
|
|
|
189
190
|
## Additional failure-mode smoke tests
|
|
190
191
|
|
package/docs/platform-smoke.md
CHANGED
|
@@ -90,7 +90,7 @@ On each required target, `platform-build`:
|
|
|
90
90
|
5. runs `npm pack`;
|
|
91
91
|
6. creates a fresh target-local pi project;
|
|
92
92
|
7. runs `npm install --no-save <packed tarball>`;
|
|
93
|
-
8. runs `pi install -l ./node_modules/pi-oracle --approve` so Pi 0.79.
|
|
93
|
+
8. runs `pi install -l ./node_modules/pi-oracle --approve` so Pi 0.79.1 project-trust gating intentionally trusts the temporary fixture;
|
|
94
94
|
9. runs `pi list --approve`;
|
|
95
95
|
10. asserts the installed package came from `node_modules/pi-oracle` and did not use `pi -e` / source-extension shortcuts.
|
|
96
96
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Scope: Extension entrypoint only; lifecycle mutation lives in lib modules and browser execution lives in worker scripts.
|
|
4
4
|
// Usage: Loaded by pi as the extension module declared in package.json.
|
|
5
5
|
// Invariants/Assumptions: Oracle only runs against persisted sessions, and startup maintenance should be best-effort without breaking session initialization.
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { dirname, join } from "node:path";
|
|
8
9
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -10,15 +11,59 @@ import { loadOracleConfig } from "./lib/config.js";
|
|
|
10
11
|
import { registerOracleCommands } from "./lib/commands.js";
|
|
11
12
|
import { getSessionFile, pruneTerminalOracleJobs, reconcileStaleOracleJobs } from "./lib/jobs.js";
|
|
12
13
|
import { isLockTimeoutError, withGlobalReconcileLock } from "./lib/locks.js";
|
|
13
|
-
import { refreshOracleStatus, startPoller, stopPoller } from "./lib/poller.js";
|
|
14
|
+
import { refreshOracleStatus, setOracleReadiness, startPoller, stopPoller } from "./lib/poller.js";
|
|
14
15
|
import { promoteQueuedJobs } from "./lib/queue.js";
|
|
15
|
-
import { hasPersistedSessionFile } from "./lib/runtime.js";
|
|
16
|
+
import { assertOracleSubmitPrerequisites, hasPersistedSessionFile } from "./lib/runtime.js";
|
|
16
17
|
import { registerOracleTools } from "./lib/tools.js";
|
|
17
18
|
|
|
19
|
+
function readPromptTemplate(path: string): string | undefined {
|
|
20
|
+
try {
|
|
21
|
+
return readFileSync(path, "utf8").replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
22
|
+
} catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function oracleReadinessFromError(error: unknown): "auth_needed" | "config_error" {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
return /auth seed profile/i.test(message) ? "auth_needed" : "config_error";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isProjectTrusted(ctx: ExtensionContext): boolean {
|
|
33
|
+
return (ctx as { isProjectTrusted?: () => boolean }).isProjectTrusted?.() ?? true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function expandOraclePromptTemplate(source: string, args: string): string {
|
|
37
|
+
return source.replaceAll("$@", args).replaceAll("$ARGUMENTS", args);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseOracleInput(text: string): { command: "oracle" | "oracle-followup"; args: string } | undefined {
|
|
41
|
+
const match = text.match(/^\/(oracle(?:-followup)?)(?:\s+([\s\S]*))?$/);
|
|
42
|
+
if (!match) return undefined;
|
|
43
|
+
return { command: match[1] as "oracle" | "oracle-followup", args: (match[2] ?? "").trim() };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatOracleUserCommand(command: "oracle" | "oracle-followup", args: string): string {
|
|
47
|
+
return `/${command} ${args}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function oracleDispatchMessage(command: "oracle" | "oracle-followup", args: string, template: string) {
|
|
51
|
+
return {
|
|
52
|
+
customType: "oracle-dispatch-request",
|
|
53
|
+
content: expandOraclePromptTemplate(template, args),
|
|
54
|
+
display: false,
|
|
55
|
+
details: { command, userRequest: args },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
export default function oracleExtension(pi: ExtensionAPI) {
|
|
19
60
|
const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
20
61
|
const workerPath = join(extensionDir, "worker", "run-job.mjs");
|
|
21
62
|
const authWorkerPath = join(extensionDir, "worker", "auth-bootstrap.mjs");
|
|
63
|
+
const promptDir = join(extensionDir, "..", "..", "prompts");
|
|
64
|
+
|
|
65
|
+
const oraclePrompt = readPromptTemplate(join(promptDir, "oracle.md"));
|
|
66
|
+
const oracleFollowupPrompt = readPromptTemplate(join(promptDir, "oracle-followup.md"));
|
|
22
67
|
|
|
23
68
|
registerOracleCommands(pi, authWorkerPath, workerPath);
|
|
24
69
|
registerOracleTools(pi, workerPath, authWorkerPath);
|
|
@@ -49,7 +94,11 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
49
94
|
return;
|
|
50
95
|
}
|
|
51
96
|
|
|
52
|
-
const config = loadOracleConfig(ctx.cwd);
|
|
97
|
+
const config = loadOracleConfig(ctx.cwd, { projectConfigTrusted: isProjectTrusted(ctx) });
|
|
98
|
+
setOracleReadiness(ctx, "loaded");
|
|
99
|
+
void assertOracleSubmitPrerequisites(config)
|
|
100
|
+
.then(() => setOracleReadiness(ctx, "ready"))
|
|
101
|
+
.catch((error) => setOracleReadiness(ctx, oracleReadinessFromError(error)));
|
|
53
102
|
void runStartupMaintenance(ctx).catch((error) => {
|
|
54
103
|
const message = `Oracle startup maintenance failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
55
104
|
console.error(message);
|
|
@@ -60,13 +109,44 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
60
109
|
} catch (error) {
|
|
61
110
|
const message = error instanceof Error ? error.message : String(error);
|
|
62
111
|
stopPoller(ctx);
|
|
112
|
+
setOracleReadiness(ctx, "config_error");
|
|
63
113
|
if (ctx.hasUI) {
|
|
64
|
-
ctx.ui.setStatus("oracle", ctx.ui.theme.fg("error", "oracle: config error"));
|
|
65
114
|
ctx.ui.notify(message, "warning");
|
|
66
115
|
}
|
|
67
116
|
}
|
|
68
117
|
}
|
|
69
118
|
|
|
119
|
+
pi.on("resources_discover", async (_event, ctx) => {
|
|
120
|
+
return ["print", "json", "rpc"].includes(ctx.mode) ? { promptPaths: [promptDir] } : undefined;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
pi.on("before_agent_start", async (event) => {
|
|
124
|
+
const parsed = parseOracleInput(event.prompt);
|
|
125
|
+
if (!parsed?.args || (parsed.command === "oracle-followup" && !/^\S+\s+\S/.test(parsed.args))) return;
|
|
126
|
+
const template = parsed.command === "oracle" ? oraclePrompt : oracleFollowupPrompt;
|
|
127
|
+
if (!template?.trim()) return;
|
|
128
|
+
return { message: oracleDispatchMessage(parsed.command, parsed.args, template) };
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
pi.on("input", (event, ctx) => {
|
|
132
|
+
if (ctx.mode !== "tui" || event.source !== "interactive") return { action: "continue" };
|
|
133
|
+
const parsed = parseOracleInput(event.text);
|
|
134
|
+
if (!parsed) return { action: "continue" };
|
|
135
|
+
if (!parsed.args || (parsed.command === "oracle-followup" && !/^\S+\s+\S/.test(parsed.args))) {
|
|
136
|
+
ctx.ui.notify(parsed.command === "oracle" ? "Usage: /oracle <request>" : "Usage: /oracle-followup <job-id> <request>", "warning");
|
|
137
|
+
return { action: "handled" };
|
|
138
|
+
}
|
|
139
|
+
const template = parsed.command === "oracle" ? oraclePrompt : oracleFollowupPrompt;
|
|
140
|
+
if (!template?.trim()) {
|
|
141
|
+
ctx.ui.notify(`/${parsed.command} is unavailable because its internal dispatch prompt could not be loaded.`, "warning");
|
|
142
|
+
return { action: "handled" };
|
|
143
|
+
}
|
|
144
|
+
ctx.ui.notify("Preparing oracle job… running preflight", "info");
|
|
145
|
+
const delivery = event.streamingBehavior ? { deliverAs: event.streamingBehavior } : undefined;
|
|
146
|
+
pi.sendUserMessage(formatOracleUserCommand(parsed.command, parsed.args), delivery);
|
|
147
|
+
return { action: "handled" };
|
|
148
|
+
});
|
|
149
|
+
|
|
70
150
|
pi.on("session_start", async (_event, ctx) => {
|
|
71
151
|
startPollerForContext(ctx);
|
|
72
152
|
});
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
// Usage: Imported by oracle commands and tools whenever the shared oracle auth seed profile must be refreshed.
|
|
5
5
|
// Invariants/Assumptions: Auth bootstrap runs under the global reconcile lock when available, uses the effective oracle config for the current workspace root, and returns the worker's stdout/stderr message verbatim on success or failure.
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
-
import { formatOracleAuthConfigRemediation, formatOracleAuthConfigSummary, getOracleConfigLoadDetails, loadOracleConfig, resolveOracleConfigForProvider, type OracleProvider } from "./config.js";
|
|
7
|
+
import { formatOracleAuthConfigRemediation, formatOracleAuthConfigSummary, getOracleConfigLoadDetails, loadOracleConfig, resolveOracleConfigForProvider, type OracleConfigLoadOptions, type OracleProvider } from "./config.js";
|
|
8
8
|
import { pruneTerminalOracleJobs, reconcileStaleOracleJobs } from "./jobs.js";
|
|
9
9
|
import { isLockTimeoutError, withGlobalReconcileLock } from "./locks.js";
|
|
10
10
|
|
|
11
|
-
export async function runOracleAuthBootstrap(authWorkerPath: string, cwd: string, provider?: OracleProvider): Promise<string> {
|
|
12
|
-
const baseConfig = loadOracleConfig(cwd);
|
|
11
|
+
export async function runOracleAuthBootstrap(authWorkerPath: string, cwd: string, provider?: OracleProvider, configOptions?: OracleConfigLoadOptions): Promise<string> {
|
|
12
|
+
const baseConfig = loadOracleConfig(cwd, configOptions);
|
|
13
13
|
const config = resolveOracleConfigForProvider(baseConfig, provider ?? baseConfig.defaults.provider);
|
|
14
|
-
const configLoad = getOracleConfigLoadDetails(cwd);
|
|
14
|
+
const configLoad = getOracleConfigLoadDetails(cwd, configOptions);
|
|
15
15
|
const authConfigGuidance = {
|
|
16
16
|
...configLoad,
|
|
17
17
|
remediation: formatOracleAuthConfigRemediation(configLoad),
|
|
@@ -27,6 +27,11 @@ import { refreshOracleStatus } from "./poller.js";
|
|
|
27
27
|
import { isLockTimeoutError, withGlobalReconcileLock } from "./locks.js";
|
|
28
28
|
import { getProjectId } from "./runtime.js";
|
|
29
29
|
|
|
30
|
+
export interface OracleCommandPromptTemplates {
|
|
31
|
+
oracle?: string;
|
|
32
|
+
oracleFollowup?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
async function summarizeJob(jobId: string, options?: { responsePreview?: boolean }): Promise<string> {
|
|
31
36
|
const job = readJob(jobId);
|
|
32
37
|
if (!job) return `Oracle job ${jobId} not found.`;
|
|
@@ -74,18 +79,30 @@ function parseOracleAuthProvider(args: string): OracleProvider | undefined {
|
|
|
74
79
|
throw new Error("Usage: /oracle-auth [chatgpt|grok]");
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
function isProjectTrusted(ctx: ExtensionCommandContext): boolean {
|
|
83
|
+
return (ctx as { isProjectTrusted?: () => boolean }).isProjectTrusted?.() ?? true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function emitCommandOutput(ctx: ExtensionCommandContext, message: string, level: "info" | "warning" | "error" = "info"): void {
|
|
87
|
+
if (ctx.mode === "print") {
|
|
88
|
+
process.stdout.write(`${message}\n`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
ctx.ui.notify(message, level);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string, workerPath: string, _promptTemplates: OracleCommandPromptTemplates = {}): void {
|
|
78
95
|
pi.registerCommand("oracle-auth", {
|
|
79
96
|
description: "Sync ChatGPT or Grok cookies from the configured local browser profile into the provider auth seed profile",
|
|
80
97
|
handler: async (args, ctx) => {
|
|
81
98
|
try {
|
|
82
99
|
const provider = parseOracleAuthProvider(args);
|
|
83
100
|
const providerLabel = provider === "grok" ? "Grok" : provider === "chatgpt" ? "ChatGPT" : "configured provider";
|
|
84
|
-
ctx
|
|
85
|
-
const result = await runOracleAuthBootstrap(authWorkerPath, ctx.cwd, provider);
|
|
86
|
-
ctx
|
|
101
|
+
emitCommandOutput(ctx, `Syncing ${providerLabel} cookies from the configured local browser profile into the oracle auth seed profile…`, "info");
|
|
102
|
+
const result = await runOracleAuthBootstrap(authWorkerPath, ctx.cwd, provider, { projectConfigTrusted: isProjectTrusted(ctx) });
|
|
103
|
+
emitCommandOutput(ctx, result, "info");
|
|
87
104
|
} catch (error) {
|
|
88
|
-
ctx
|
|
105
|
+
emitCommandOutput(ctx, error instanceof Error ? error.message : String(error), "warning");
|
|
89
106
|
}
|
|
90
107
|
},
|
|
91
108
|
});
|
|
@@ -96,12 +113,12 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
96
113
|
const explicitJobId = args.trim();
|
|
97
114
|
const jobId = explicitJobId || getLatestJobId(ctx.cwd);
|
|
98
115
|
if (!jobId) {
|
|
99
|
-
ctx
|
|
116
|
+
emitCommandOutput(ctx, "No oracle jobs found for this project", "info");
|
|
100
117
|
return;
|
|
101
118
|
}
|
|
102
119
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
103
120
|
if (!job) {
|
|
104
|
-
ctx
|
|
121
|
+
emitCommandOutput(ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
105
122
|
return;
|
|
106
123
|
}
|
|
107
124
|
if (isTerminalOracleJob(job)) {
|
|
@@ -113,7 +130,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
113
130
|
}
|
|
114
131
|
const summary = await summarizeJob(job.id);
|
|
115
132
|
const recentJobs = !explicitJobId ? listRecentJobIds(ctx.cwd) : undefined;
|
|
116
|
-
ctx
|
|
133
|
+
emitCommandOutput(ctx, [summary, recentJobs ? `Recent jobs: ${recentJobs}` : undefined].filter(Boolean).join("\n"), "info");
|
|
117
134
|
},
|
|
118
135
|
});
|
|
119
136
|
|
|
@@ -123,12 +140,12 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
123
140
|
const explicitJobId = args.trim();
|
|
124
141
|
const jobId = explicitJobId || getLatestJobId(ctx.cwd);
|
|
125
142
|
if (!jobId) {
|
|
126
|
-
ctx
|
|
143
|
+
emitCommandOutput(ctx, "No oracle jobs found for this project", "info");
|
|
127
144
|
return;
|
|
128
145
|
}
|
|
129
146
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
130
147
|
if (!job) {
|
|
131
|
-
ctx
|
|
148
|
+
emitCommandOutput(ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
132
149
|
return;
|
|
133
150
|
}
|
|
134
151
|
if (isTerminalOracleJob(job)) {
|
|
@@ -138,7 +155,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
138
155
|
cwd: ctx.cwd,
|
|
139
156
|
});
|
|
140
157
|
}
|
|
141
|
-
ctx
|
|
158
|
+
emitCommandOutput(ctx, await summarizeJob(job.id, { responsePreview: true }), "info");
|
|
142
159
|
},
|
|
143
160
|
});
|
|
144
161
|
|
|
@@ -147,17 +164,21 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
147
164
|
handler: async (args, ctx) => {
|
|
148
165
|
const jobId = args.trim();
|
|
149
166
|
if (!jobId) {
|
|
150
|
-
ctx
|
|
167
|
+
emitCommandOutput(ctx, "Usage: /oracle-cancel <job-id>\nUse /oracle-status to find the job id you want to cancel.", "warning");
|
|
151
168
|
return;
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
155
172
|
if (!job) {
|
|
156
|
-
ctx
|
|
173
|
+
emitCommandOutput(ctx, `Oracle job ${jobId} not found in this project`, "warning");
|
|
157
174
|
return;
|
|
158
175
|
}
|
|
159
176
|
if (!isOpenOracleJob(job)) {
|
|
160
|
-
|
|
177
|
+
if (isTerminalOracleJob(job)) {
|
|
178
|
+
emitCommandOutput(ctx, `Job is already terminal: ${job.status}. Use /oracle-read ${job.id} for details or /oracle-clean ${job.id} to remove it.`, "info");
|
|
179
|
+
} else {
|
|
180
|
+
emitCommandOutput(ctx, `Oracle job ${jobId} is not cancellable (${job.status})`, "info");
|
|
181
|
+
}
|
|
161
182
|
return;
|
|
162
183
|
}
|
|
163
184
|
|
|
@@ -166,7 +187,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
166
187
|
await promoteQueuedJobs({ workerPath, source: "oracle_cancel_command" });
|
|
167
188
|
}
|
|
168
189
|
refreshOracleStatus(ctx);
|
|
169
|
-
ctx
|
|
190
|
+
emitCommandOutput(ctx, formatOracleCancelOutcome(cancelled), "info");
|
|
170
191
|
},
|
|
171
192
|
});
|
|
172
193
|
|
|
@@ -175,19 +196,20 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
175
196
|
handler: async (args, ctx: ExtensionCommandContext) => {
|
|
176
197
|
const target = args.trim();
|
|
177
198
|
if (!target) {
|
|
178
|
-
ctx
|
|
199
|
+
emitCommandOutput(ctx, "Usage: /oracle-clean <job-id|all>", "warning");
|
|
179
200
|
return;
|
|
180
201
|
}
|
|
181
202
|
|
|
182
203
|
const jobs = target === "all" ? listJobsForCwd(ctx.cwd) : [readScopedJob(target, ctx.cwd)].filter(Boolean);
|
|
183
204
|
if (jobs.length === 0) {
|
|
184
|
-
ctx
|
|
205
|
+
emitCommandOutput(ctx, "No matching oracle jobs found", "warning");
|
|
185
206
|
return;
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
const nonTerminalJobs = jobs.filter((job): job is NonNullable<typeof job> => Boolean(job && !isTerminalOracleJob(job)));
|
|
189
210
|
if (nonTerminalJobs.length > 0) {
|
|
190
|
-
|
|
211
|
+
emitCommandOutput(
|
|
212
|
+
ctx,
|
|
191
213
|
`Refusing to remove non-terminal oracle job${nonTerminalJobs.length === 1 ? "" : "s"}: ${nonTerminalJobs.map((job) => job.id).join(", ")}`,
|
|
192
214
|
"warning",
|
|
193
215
|
);
|
|
@@ -217,10 +239,14 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
217
239
|
|
|
218
240
|
refreshOracleStatus(ctx);
|
|
219
241
|
const warningSuffix = cleanupWarnings.length > 0 ? ` Cleanup blockers/warnings:\n${cleanupWarnings.join("\n")}` : "";
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
242
|
+
const retentionWarning = cleanupWarnings.find((warning) => warning.includes("post-send retention grace window") && warning.includes("Retry after "));
|
|
243
|
+
const retryAfter = retentionWarning?.match(/Retry after ([^ ]+)/)?.[1];
|
|
244
|
+
const removalSummary = retryAfter && removedCount < jobs.length
|
|
245
|
+
? `Job retained for wake-up safety. Retry cleanup after ${retryAfter}.`
|
|
246
|
+
: removedCount === jobs.length
|
|
247
|
+
? `Removed ${removedCount} oracle job director${removedCount === 1 ? "y" : "ies"}.`
|
|
248
|
+
: `Removed ${removedCount} of ${jobs.length} oracle job director${jobs.length === 1 ? "y" : "ies"}; retained ${jobs.length - removedCount} due to cleanup blockers or warnings.`;
|
|
249
|
+
emitCommandOutput(ctx, `${removalSummary}${warningSuffix}`, cleanupWarnings.length > 0 ? "warning" : "info");
|
|
224
250
|
},
|
|
225
251
|
});
|
|
226
252
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
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
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { buildOracleStatusText, buildOracleWakeupNotificationContent } from "../shared/job-observability-helpers.mjs";
|
|
8
|
+
import { buildOracleStatusText, buildOracleWakeupNotificationContent, type OracleReadinessStatus } 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";
|
|
11
11
|
import {
|
|
@@ -46,6 +46,7 @@ interface OraclePollerLifecycle {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const activePollers = new Map<string, OracleActivePoller>();
|
|
49
|
+
const readinessBySession = new Map<string, OracleReadinessStatus>();
|
|
49
50
|
const scansInFlight = new Set<string>();
|
|
50
51
|
const POLLER_LOCK_TIMEOUT_MS = 50;
|
|
51
52
|
const WAKEUP_TARGET_LEASE_KIND = "wakeup-target";
|
|
@@ -167,19 +168,31 @@ function getJobCountsForSession(sessionFile: string | undefined, cwd: string): {
|
|
|
167
168
|
function refreshOracleStatusSnapshot(snapshot: OraclePollerContextSnapshot): void {
|
|
168
169
|
if (!snapshot.hasUI) return;
|
|
169
170
|
if (!snapshot.sessionFile) {
|
|
170
|
-
snapshot.ui.setStatus("oracle", snapshot.ui.theme.fg("
|
|
171
|
+
snapshot.ui.setStatus("oracle", snapshot.ui.theme.fg("error", "oracle: unavailable"));
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
173
174
|
const counts = getJobCountsForSession(snapshot.sessionFile, snapshot.cwd);
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
175
|
+
const readiness = readinessBySession.get(getPollerSessionKey(snapshot.sessionFile, snapshot.cwd)) ?? "loaded";
|
|
176
|
+
const statusText = buildOracleStatusText(counts, readiness);
|
|
177
|
+
if (counts.active > 0) {
|
|
178
|
+
snapshot.ui.setStatus("oracle", snapshot.ui.theme.fg("success", statusText));
|
|
179
|
+
} else if (readiness === "auth_needed" || readiness === "config_error") {
|
|
180
|
+
snapshot.ui.setStatus("oracle", snapshot.ui.theme.fg("error", statusText));
|
|
181
|
+
} else {
|
|
182
|
+
snapshot.ui.setStatus("oracle", statusText);
|
|
183
|
+
}
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
export function refreshOracleStatus(ctx: ExtensionContext): void {
|
|
180
187
|
refreshOracleStatusSnapshot(snapshotPollerContext(ctx));
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
export function setOracleReadiness(ctx: ExtensionContext, readiness: OracleReadinessStatus): void {
|
|
191
|
+
const snapshot = snapshotPollerContext(ctx);
|
|
192
|
+
if (snapshot.sessionFile) readinessBySession.set(getPollerSessionKey(snapshot.sessionFile, snapshot.cwd), readiness);
|
|
193
|
+
refreshOracleStatusSnapshot(snapshot);
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
function requestWakeupTurn(pi: ExtensionAPI, job: OraclePollerJob): void {
|
|
184
197
|
pi.sendMessage(
|
|
185
198
|
{
|
|
@@ -412,6 +425,7 @@ export function stopPollerForSession(sessionFile: string | undefined, cwd: strin
|
|
|
412
425
|
if (handle.timer) clearInterval(handle.timer);
|
|
413
426
|
activePollers.delete(sessionKey);
|
|
414
427
|
}
|
|
428
|
+
readinessBySession.delete(sessionKey);
|
|
415
429
|
const wakeupTargetLeaseKey = getWakeupTargetLeaseKey(sessionKey);
|
|
416
430
|
void releaseLease(WAKEUP_TARGET_LEASE_KIND, wakeupTargetLeaseKey).catch(() => undefined);
|
|
417
431
|
}
|
|
@@ -423,6 +437,7 @@ export async function stopAllPollers(): Promise<void> {
|
|
|
423
437
|
if (handle.timer) clearInterval(handle.timer);
|
|
424
438
|
}
|
|
425
439
|
activePollers.clear();
|
|
440
|
+
readinessBySession.clear();
|
|
426
441
|
await Promise.all(handles.map(async (handle) => {
|
|
427
442
|
const wakeupTargetLeaseKey = getWakeupTargetLeaseKey(handle.sessionKey);
|
|
428
443
|
await releaseLease(WAKEUP_TARGET_LEASE_KIND, wakeupTargetLeaseKey).catch(() => undefined);
|