pi-oracle 0.7.5 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.7.7 - 2026-06-08
6
+
7
+ ### Changed
8
+ - updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-ai` `0.79.0` and regenerated the npm lockfile
9
+ - documented `pi` `0.79.0+` 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.0 project-trust rules
11
+
12
+ ### Fixed
13
+ - made project-local `.pi/extensions/oracle.json` overrides honor explicit Pi project-trust opt-outs (`--no-approve` or a saved “do not trust” decision) while preserving the historical default of loading safe project overrides for existing oracle users
14
+
15
+ ### Compatibility
16
+ - reviewed the pi `0.79.0` 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
17
+
18
+ ## 0.7.6 - 2026-06-04
19
+
20
+ ### Changed
21
+ - updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-ai` `0.78.1` and regenerated the npm lockfile
22
+ - documented `pi` `0.78.1+` as the suggested tested floor while keeping pi runtime packages as optional wildcard peers so newer pi releases are not blocked by npm peer ranges
23
+ - added Ant Ling, NVIDIA NIM, and MiniMax China provider env mappings to the oracle real-smoke/platform-smoke provider metadata
24
+
25
+ ### Fixed
26
+ - made startup poller and wake-up routing mode-aware with Pi 0.78.1 `ctx.mode`, so print and JSON one-shot runs do not start background pollers or publish oracle UI status
27
+ - guarded oracle startup status and warning notifications with `ctx.hasUI` for non-UI modes
28
+
29
+ ### Compatibility
30
+ - reviewed the pi `0.78.1` changelog, extension docs, package docs, prompt-template docs, and matching examples; the oracle extension remains compatible with current extension lifecycle and package install/update behavior
31
+
5
32
  ## 0.7.5 - 2026-06-02
6
33
 
7
34
  ### Added
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 the current pi package baseline. Pi-bundled runtime packages are optional wildcard peers so npm peer ranges do not block users from trying newer pi releases, though runtime behavior is only verified against the tested baseline until a follow-up package release confirms it. Normal oracle jobs run in an isolated browser profile, not your active browser window.
5
+ > Status: experimental public beta. Validated on macOS, Linux, and Windows native with Chromium-family browsers and pi `0.79.0`. Pi `0.79.0+` 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
- - `pi` 0.65.0 or newer
80
+ - Suggested tested floor: `pi` 0.79.0 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
@@ -179,6 +179,8 @@ 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.0 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
+
182
184
  `~/.pi/agent/extensions/oracle.json`
183
185
 
184
186
  ```json
@@ -383,12 +385,13 @@ Use the narrowest validation workflow that proves the change:
383
385
  | --- | --- |
384
386
  | Everyday local iteration | `npm run verify:oracle` |
385
387
  | Platform-sensitive changes | `npm run smoke:platform:doctor`, then a focused `node scripts/platform-smoke.mjs run --target <target> --suite <suite>` |
386
- | Publish/release proof | `npm run smoke:platform:all` |
388
+ | Platform matrix proof | `npm run smoke:platform:all` |
389
+ | Publish/release gate | `npm run release:check` |
387
390
 
388
- For macOS, Ubuntu, and Windows native package/build plus packed runtime validation, use [`docs/platform-smoke.md`](docs/platform-smoke.md). The full release proof is:
391
+ For macOS, Ubuntu, and Windows native package/build plus packed runtime validation, use [`docs/platform-smoke.md`](docs/platform-smoke.md). The full release gate is:
389
392
 
390
393
  ```bash
391
- npm run smoke:platform:all
394
+ npm run release:check
392
395
  ```
393
396
 
394
397
  The real runtime suite defaults to deterministic installed-tool execution so platform proof stays bounded. Provider/model defaults remain `zai/glm-5.1` for doctor/config and for optional model-agent debugging; override with `PI_ORACLE_REAL_TEST_PROVIDER` and `PI_ORACLE_REAL_TEST_MODEL` when needed. For inner-loop source loading only, use `npm run smoke:real:source`; it is not release proof. Set `PI_ORACLE_REAL_TEST_MODEL_AGENT=1` only when debugging the slower model-agent path. The optional second real-agent negative symlink check is opt-in via `PI_ORACLE_REAL_TEST_NEGATIVE_SYMLINK=1`; `npm run sanity:oracle` covers archive/symlink rejection by default without adding another model-agent turn to the platform release gate.
@@ -1,13 +1,14 @@
1
1
  # pi-oracle design
2
2
 
3
- Status: isolated-profile concurrency architecture implemented in code; major live validation now passes, but a few non-blocking hardening items remain.
4
- Date: 2026-04-03
3
+ Status: isolated-profile concurrency architecture implemented in code and validated against the current pi baseline.
4
+ Date: 2026-06-04
5
5
 
6
6
  Companion doc:
7
7
  - `docs/ORACLE_RECOVERY_DRILL.md` — safe expired-auth recovery validation drill
8
8
 
9
9
  Compatibility target:
10
- - `pi` 0.65.0+
10
+ - `pi` 0.79.0+ is the suggested tested floor for current project-trust-aware package/runtime validation
11
+ - package metadata keeps pi runtime packages as optional wildcard peers, so this suggested floor is not enforced as a hard npm install requirement
11
12
  - current extension lifecycle only; no backward-compatibility shims for removed `session_switch` / `session_fork` events
12
13
 
13
14
  ## Goal
@@ -228,9 +229,7 @@ Merged config locations:
228
229
  - global: `~/.pi/agent/extensions/oracle.json`
229
230
  - project: `.pi/extensions/oracle.json`
230
231
 
231
- Project config remains restricted to safe overrides only.
232
-
233
- Browser/auth settings are global-only because they control local privileged browser state.
232
+ Project config remains restricted to safe overrides only. On Pi 0.79.0+, 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 only when the run passes `--no-approve` or the project has a saved “do not trust” decision. 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.
234
233
 
235
234
  ### Current config shape
236
235
 
@@ -632,11 +631,15 @@ Remaining non-blocking hardening work:
632
631
  - keep hardening model-selection verification against future ChatGPT UI variation
633
632
 
634
633
  Recent proof points:
634
+ - 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
+ - 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
+ - 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`
637
+ - Pi 0.79.0 packed-install smoke: `.artifacts/real-smoke/run-1780935825537-pmna07` passed with `npm run smoke:real:packed`
635
638
  - expired-auth drill fail path: `a2460bc1-7d89-4041-b67d-39680d310325`
636
639
  - `/oracle-auth` repair evidence: the per-run `/tmp/pi-oracle-auth-*/oracle-auth.log` bundle path printed by `/oracle-auth`
637
640
  - expired-auth drill post-repair success: `fa26a2a7-0057-4a21-b3e0-71c1d020facf`
638
641
  - successful multi-artifact completion: `b6b3599c-6b91-4315-adfa-8a83aa5eda9b`
639
642
  - repo-owned sanity harness: `npm run sanity:oracle`
640
643
  - real installed-extension smoke source of truth: `scripts/oracle-real-smoke.mjs`; required release proof runs packed-install mode (`npm run smoke:real:packed`) and executes installed-package `oracle_submit` deterministically, with optional slower model-agent debugging via `PI_ORACLE_REAL_TEST_MODEL_AGENT=1`; source mode (`npm run smoke:real:source`) is inner-loop/debug only
641
- - macOS, Ubuntu, and Windows native package/build/runtime smoke source of truth: `docs/platform-smoke.md`; use `npm run verify:oracle` for everyday local iteration, `npm run smoke:platform:doctor` plus a focused target/suite run for platform-sensitive changes, and `npm run smoke:platform:all` for doctor-first packed-install Crabbox release evidence
644
+ - macOS, Ubuntu, and Windows native package/build/runtime smoke source of truth: `docs/platform-smoke.md`; use `npm run verify:oracle` for everyday local iteration, `npm run smoke:platform:doctor` plus a focused target/suite run for platform-sensitive changes, `npm run smoke:platform:all` for doctor-first platform matrix evidence, and `npm run release:check` for the full local-plus-platform release gate
642
645
  - release gate: `npm run release:check`, also used by `prepublishOnly`, combines static verification and all required Crabbox platform smokes
@@ -24,15 +24,15 @@ That keeps the validation run from reusing your normal `pi` agent state.
24
24
  The extension is loaded from the local checkout with:
25
25
 
26
26
  ```bash
27
- pi --no-extensions -e "$REPO/extensions/oracle/index.ts"
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.
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.0+: the test fixture is this trusted checkout, and non-interactive/scripted validation must not block on the project-trust prompt.
31
31
 
32
32
  If you also need the in-repo `/oracle` prompt template, load it explicitly instead of installing this repository as a project-local package:
33
33
 
34
34
  ```bash
35
- pi --no-extensions -e "$REPO/extensions/oracle/index.ts" \
35
+ pi --approve --no-extensions -e "$REPO/extensions/oracle/index.ts" \
36
36
  --no-prompt-templates --prompt-template "$REPO/prompts/oracle.md"
37
37
  ```
38
38
 
@@ -96,7 +96,7 @@ cleanup() {
96
96
  trap cleanup EXIT
97
97
  cleanup
98
98
 
99
- TMUX_CMD1="cd '$REPO' && env PI_CODING_AGENT_DIR='$TEST1_AGENT' PI_ORACLE_JOBS_DIR='$TEST1_JOBS' PATH='$PATH' pi --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
99
+ TMUX_CMD1="cd '$REPO' && env PI_CODING_AGENT_DIR='$TEST1_AGENT' PI_ORACLE_JOBS_DIR='$TEST1_JOBS' PATH='$PATH' pi --approve --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
100
100
  tmux new-session -d -s "$SESSION1" "$TMUX_CMD1"
101
101
  sleep 8
102
102
  tmux send-keys -t "$SESSION1":0.0 "$PROMPT1" Enter
@@ -129,7 +129,7 @@ PY
129
129
  rm -f "$LIST"
130
130
  fi
131
131
 
132
- TMUX_CMD2="cd '$FIXTURE' && env PI_CODING_AGENT_DIR='$TEST2_AGENT' PI_ORACLE_JOBS_DIR='$TEST2_JOBS' PATH='$PATH' pi --session-dir '$TEST2_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
132
+ TMUX_CMD2="cd '$FIXTURE' && env PI_CODING_AGENT_DIR='$TEST2_AGENT' PI_ORACLE_JOBS_DIR='$TEST2_JOBS' PATH='$PATH' pi --approve --session-dir '$TEST2_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
133
133
  tmux new-session -d -s "$SESSION2" "$TMUX_CMD2"
134
134
  sleep 8
135
135
  tmux send-keys -t "$SESSION2":0.0 "$PROMPT2" Enter
@@ -178,7 +178,7 @@ Expected behavior:
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 template explicitly loaded:
179
179
 
180
180
  ```bash
181
- LOCAL_ORACLE_PI_CMD="pi --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts' --no-prompt-templates --prompt-template '$REPO/prompts/oracle.md'"
181
+ LOCAL_ORACLE_PI_CMD="pi --approve --session-dir '$TEST1_SESSIONS' --no-extensions -e '$REPO/extensions/oracle/index.ts' --no-prompt-templates --prompt-template '$REPO/prompts/oracle.md'"
182
182
  TMUX_CMD1="cd '$REPO' && env PI_CODING_AGENT_DIR='$TEST1_AGENT' PI_ORACLE_JOBS_DIR='$TEST1_JOBS' PATH='$PATH' $LOCAL_ORACLE_PI_CMD"
183
183
  ```
184
184
 
@@ -226,7 +226,7 @@ cleanup() {
226
226
  trap 'cleanup; rm -rf "$TEST_ROOT"' EXIT
227
227
  cleanup
228
228
 
229
- TMUX_CMD="cd '$REPO' && env PI_CODING_AGENT_DIR='$AGENT_DIR' PI_ORACLE_JOBS_DIR='$JOBS_DIR' AGENT_BROWSER_PATH='$FAKE_BROWSER' PI_ORACLE_AUTH_AGENT_BROWSER_TIMEOUT_MS='250' PI_ORACLE_AUTH_CLOSE_TIMEOUT_MS='250' PI_ORACLE_AUTH_KILL_GRACE_MS='100' PATH='$PATH' pi --session-dir '$SESSION_DIR' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
229
+ TMUX_CMD="cd '$REPO' && env PI_CODING_AGENT_DIR='$AGENT_DIR' PI_ORACLE_JOBS_DIR='$JOBS_DIR' AGENT_BROWSER_PATH='$FAKE_BROWSER' PI_ORACLE_AUTH_AGENT_BROWSER_TIMEOUT_MS='250' PI_ORACLE_AUTH_CLOSE_TIMEOUT_MS='250' PI_ORACLE_AUTH_KILL_GRACE_MS='100' PATH='$PATH' pi --approve --session-dir '$SESSION_DIR' --no-extensions -e '$REPO/extensions/oracle/index.ts'"
230
230
 
231
231
  tmux new-session -d -s "$SESSION_NAME" "$TMUX_CMD"
232
232
  sleep 8
@@ -13,7 +13,7 @@
13
13
 
14
14
  Required targets: `macos`, `ubuntu`, `windows-native`.
15
15
  Required suites: `platform-build`, `real-extension`.
16
- Crabbox baseline: `0.24.0` or newer.
16
+ Crabbox baseline: `0.26.0` or newer.
17
17
 
18
18
  ## Required local setup
19
19
 
@@ -25,7 +25,7 @@ crabbox --version
25
25
  crabbox providers
26
26
  ```
27
27
 
28
- `PI_ORACLE_SMOKE_CRABBOX` is optional and only needed to override the binary path.
28
+ `PLATFORM_SMOKE_CRABBOX` is the reusable binary override. `PI_ORACLE_SMOKE_CRABBOX` is a project-specific alias and wins when both are set.
29
29
 
30
30
  Target setup:
31
31
 
@@ -48,13 +48,14 @@ Use the narrowest workflow that proves the change. Do not run the full platform
48
48
  | --- | --- | --- |
49
49
  | Everyday local iteration | `npm run verify:oracle` | Syntax, bundle, platform-smoke invariants, type checks, oracle sanity, and package dry-run pass locally. |
50
50
  | Platform-sensitive change | `npm run smoke:platform:doctor`, then `node scripts/platform-smoke.mjs run --target <target> --suite <suite>` | Target setup is ready and the affected platform/suite works without paying for unrelated targets. |
51
- | Publish/release proof | `npm run smoke:platform:all` | Doctor-first packed-install proof passes on every required target and suite. |
51
+ | Platform matrix proof | `npm run smoke:platform:all` | Doctor-first packed-install proof passes on every required target and suite. |
52
+ | Publish/release gate | `npm run release:check` | Local verification (`verify:oracle`) passes, then the doctor-first platform matrix passes. |
52
53
 
53
54
  Platform-sensitive changes include archive behavior, process cleanup, runtime/browser profile handling, package metadata, Crabbox harness code, or anything that may differ across macOS/Linux/Windows.
54
55
 
55
56
  ## Commands
56
57
 
57
- Doctor is mandatory before the full release matrix. The canonical all-target release command enforces that:
58
+ Doctor is mandatory before the full platform matrix. The canonical all-target platform command enforces that:
58
59
 
59
60
  ```bash
60
61
  npm run smoke:platform:all
@@ -70,13 +71,13 @@ npm run smoke:platform:windows-native
70
71
  node scripts/platform-smoke.mjs run --target windows-native --suite real-extension
71
72
  ```
72
73
 
73
- Release check:
74
+ Full release gate:
74
75
 
75
76
  ```bash
76
77
  npm run release:check
77
78
  ```
78
79
 
79
- `prepublishOnly` runs `npm run release:check`.
80
+ `release:check` runs `verify:oracle` before `smoke:platform:all`, matching the Crabbox doctor-first release order: cheap harness checks, doctor, full matrix, then artifact review. `prepublishOnly` runs `npm run release:check`.
80
81
 
81
82
  ## What `platform-build` proves
82
83
 
@@ -89,8 +90,8 @@ On each required target, `platform-build`:
89
90
  5. runs `npm pack`;
90
91
  6. creates a fresh target-local pi project;
91
92
  7. runs `npm install --no-save <packed tarball>`;
92
- 8. runs `pi install -l ./node_modules/pi-oracle`;
93
- 9. runs `pi list`;
93
+ 8. runs `pi install -l ./node_modules/pi-oracle --approve` so Pi 0.79.0 project-trust gating intentionally trusts the temporary fixture;
94
+ 9. runs `pi list --approve`;
94
95
  10. asserts the installed package came from `node_modules/pi-oracle` and did not use `pi -e` / source-extension shortcuts.
95
96
 
96
97
  ## What `real-extension` proves
@@ -99,8 +100,8 @@ On each required target, `platform-build`:
99
100
 
100
101
  1. packs this checkout with `npm pack`;
101
102
  2. installs the tarball into a clean pi project;
102
- 3. runs `pi install -l ./node_modules/pi-oracle`;
103
- 4. asserts `pi list` shows the packed install path;
103
+ 3. runs `pi install -l ./node_modules/pi-oracle --approve`;
104
+ 4. asserts `pi list --approve` shows the packed install path;
104
105
  5. executes `oracle_submit` from the installed package path, not source `pi -e`;
105
106
  6. asserts whole-project archive creation and default exclusions.
106
107
 
@@ -112,7 +113,7 @@ For inner-loop/debug only, use:
112
113
  npm run smoke:real:source
113
114
  ```
114
115
 
115
- That source-mode smoke loads `extensions/oracle/index.ts` with `pi --no-extensions -e`; it is useful while developing but is not release proof.
116
+ That source-mode smoke loads `extensions/oracle/index.ts` with `pi --approve --no-extensions -e`; it is useful while developing but is not release proof.
116
117
 
117
118
  ## Artifacts
118
119
 
@@ -142,12 +143,14 @@ Artifacts are local evidence only. Do not commit or share them without redaction
142
143
 
143
144
  ## Windows template maintenance
144
145
 
145
- When Windows lacks a reusable tool such as `zstd` or `agent-browser`, update `pi-extension-windows-template` rather than adding a per-run installer:
146
-
147
- 1. boot `pi-extension-windows-template`;
148
- 2. install/update the tool globally without secrets;
149
- 3. verify from a fresh SSH session: `node --version`, `npm --version`, `git --version`, `tar --version`, `zstd --version`, `agent-browser --version`;
150
- 4. remove downloads, caches, checkouts, `.pi`, `.artifacts`, `.debug`, and secrets;
151
- 5. shut down cleanly;
152
- 6. create/promote the configured power-off snapshot;
153
- 7. clean stale clones/leases.
146
+ When Windows lacks a reusable tool such as `zstd` or `agent-browser`, update the shared `pi-extension-windows-template` infrastructure rather than adding a per-run installer:
147
+
148
+ 1. revert/switch `pi-extension-windows-template` to the current canonical `crabbox-ready` snapshot;
149
+ 2. boot the template;
150
+ 3. install/update the reusable tool globally without secrets;
151
+ 4. verify from a fresh SSH session: `node --version`, `npm --version`, `git --version`, `tar --version`, `zstd --version`, `agent-browser --version`, and `agent-browser install`;
152
+ 5. remove downloads, caches, checkouts, `.pi`, `.artifacts`, `.debug`, browser auth/session state, and secrets;
153
+ 6. shut down cleanly;
154
+ 7. create/promote the configured power-off `crabbox-ready` snapshot;
155
+ 8. run `npm run smoke:platform:doctor` and `npm run smoke:platform:windows-native` against the promoted snapshot;
156
+ 9. clean stale clones/leases.
@@ -39,9 +39,13 @@ export default function oracleExtension(pi: ExtensionAPI) {
39
39
  function startPollerForContext(ctx: ExtensionContext) {
40
40
  try {
41
41
  const sessionFile = getSessionFile(ctx);
42
+ if (ctx.mode === "print" || ctx.mode === "json") {
43
+ stopPoller(ctx);
44
+ return;
45
+ }
42
46
  if (!hasPersistedSessionFile(sessionFile)) {
43
47
  stopPoller(ctx);
44
- ctx.ui.setStatus("oracle", ctx.ui.theme.fg("accent", "oracle: unavailable"));
48
+ if (ctx.hasUI) ctx.ui.setStatus("oracle", ctx.ui.theme.fg("accent", "oracle: unavailable"));
45
49
  return;
46
50
  }
47
51
 
@@ -49,15 +53,17 @@ export default function oracleExtension(pi: ExtensionAPI) {
49
53
  void runStartupMaintenance(ctx).catch((error) => {
50
54
  const message = `Oracle startup maintenance failed: ${error instanceof Error ? error.message : String(error)}`;
51
55
  console.error(message);
52
- ctx.ui.notify(message, "warning");
56
+ if (ctx.hasUI) ctx.ui.notify(message, "warning");
53
57
  });
54
58
  startPoller(pi, ctx, config.poller.intervalMs, workerPath);
55
59
  refreshOracleStatus(ctx);
56
60
  } catch (error) {
57
61
  const message = error instanceof Error ? error.message : String(error);
58
62
  stopPoller(ctx);
59
- ctx.ui.setStatus("oracle", ctx.ui.theme.fg("error", "oracle: config error"));
60
- ctx.ui.notify(message, "warning");
63
+ if (ctx.hasUI) {
64
+ ctx.ui.setStatus("oracle", ctx.ui.theme.fg("error", "oracle: config error"));
65
+ ctx.ui.notify(message, "warning");
66
+ }
61
67
  }
62
68
  }
63
69
 
@@ -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 "@earendil-works/pi-coding-agent";
9
+ import { getAgentDir, hasProjectTrustInputs, ProjectTrustStore } from "@earendil-works/pi-coding-agent";
10
10
  import { isAbsolute, join, normalize } from "node:path";
11
11
  import {
12
12
  assertNotKnownBrowserUserDataPath,
@@ -313,27 +313,76 @@ const detectedChromeUserAgent = detectDefaultChromeUserAgent(detectedChromeExecu
313
313
  const agentExtensionsDir = join(getAgentDir(), "extensions");
314
314
  const detectedChromeProfileName = detectDefaultBrowserProfileSource(process.platform);
315
315
 
316
+ export interface OracleConfigLoadOptions {
317
+ /**
318
+ * Whether project-local oracle config may be loaded. Omit for the runtime
319
+ * policy that preserves oracle's historical project-config behavior while
320
+ * respecting explicit --no-approve and saved distrust decisions.
321
+ */
322
+ projectConfigTrusted?: boolean;
323
+ /** Session cwd used for Pi's saved project-trust decision when config lookup is anchored to a derived project root. */
324
+ projectConfigTrustCwd?: string;
325
+ }
326
+
316
327
  export interface OracleConfigLoadDetails {
317
328
  agentDir: string;
318
329
  agentConfigPath: string;
319
330
  agentConfigExists: boolean;
320
331
  projectConfigPath: string;
321
332
  projectConfigExists: boolean;
333
+ projectConfigTrusted: boolean;
334
+ projectConfigLoaded: boolean;
335
+ projectConfigSkippedReason?: string;
322
336
  effectiveAuthConfigPath: string;
323
337
  effectiveAuthScope: "agent";
324
338
  }
325
339
 
326
- export function getOracleConfigLoadDetails(cwd: string): OracleConfigLoadDetails {
340
+ function getProjectTrustCliOverride(argv = process.argv): boolean | undefined {
341
+ let trusted: boolean | undefined;
342
+ for (const arg of argv.slice(2)) {
343
+ if (arg === "--approve" || arg === "-a") trusted = true;
344
+ if (arg === "--no-approve" || arg === "-na") trusted = false;
345
+ }
346
+ return trusted;
347
+ }
348
+
349
+ function isProjectConfigTrusted(cwd: string, agentDir: string, projectConfigExists: boolean, options?: OracleConfigLoadOptions): boolean {
350
+ if (options?.projectConfigTrusted !== undefined) return options.projectConfigTrusted;
351
+ const trustCwd = options?.projectConfigTrustCwd ?? cwd;
352
+ const cliOverride = getProjectTrustCliOverride();
353
+ if (cliOverride !== undefined) return cliOverride;
354
+ if (!projectConfigExists && !hasProjectTrustInputs(trustCwd)) return true;
355
+ try {
356
+ const trustStore = new ProjectTrustStore(agentDir);
357
+ const trustDecision = trustStore.get(trustCwd);
358
+ const rootDecision = trustCwd !== cwd ? trustStore.get(cwd) : null;
359
+ if (trustDecision !== null) return trustDecision;
360
+ if (rootDecision !== null) return rootDecision;
361
+ } catch {
362
+ return false;
363
+ }
364
+ return true;
365
+ }
366
+
367
+ export function getOracleConfigLoadDetails(cwd: string, options?: OracleConfigLoadOptions): OracleConfigLoadDetails {
327
368
  const agentDir = getAgentDir();
328
369
  const projectRoot = getProjectId(cwd);
329
370
  const agentConfigPath = join(agentDir, "extensions", "oracle.json");
330
371
  const projectConfigPath = join(projectRoot, ".pi", "extensions", "oracle.json");
372
+ const projectConfigExists = existsSync(projectConfigPath);
373
+ const projectConfigTrusted = isProjectConfigTrusted(projectRoot, agentDir, projectConfigExists, options);
374
+ const projectConfigLoaded = projectConfigExists && projectConfigTrusted;
331
375
  return {
332
376
  agentDir,
333
377
  agentConfigPath,
334
378
  agentConfigExists: existsSync(agentConfigPath),
335
379
  projectConfigPath,
336
- projectConfigExists: existsSync(projectConfigPath),
380
+ projectConfigExists,
381
+ projectConfigTrusted,
382
+ projectConfigLoaded,
383
+ projectConfigSkippedReason: projectConfigExists && !projectConfigTrusted
384
+ ? "Project oracle config is ignored because this run used --no-approve or the project has a saved untrusted decision."
385
+ : undefined,
337
386
  effectiveAuthConfigPath: agentConfigPath,
338
387
  effectiveAuthScope: "agent",
339
388
  };
@@ -341,8 +390,11 @@ export function getOracleConfigLoadDetails(cwd: string): OracleConfigLoadDetails
341
390
 
342
391
  export function formatOracleAuthConfigRemediation(details: OracleConfigLoadDetails): string {
343
392
  const authFields = "auth.chromeProfile / auth.chromeCookiePath / auth.chromiumKeychain";
344
- if (!details.projectConfigExists) {
345
- return `Set ${authFields} in ${details.effectiveAuthConfigPath}.`;
393
+ if (!details.projectConfigLoaded) {
394
+ const projectNote = details.projectConfigSkippedReason
395
+ ? ` Project config at ${details.projectConfigPath} is present but not loaded because this run explicitly does not trust project-local inputs.`
396
+ : "";
397
+ return `Set ${authFields} in ${details.effectiveAuthConfigPath}.${projectNote}`;
346
398
  }
347
399
  return (
348
400
  `Set ${authFields} in ${details.effectiveAuthConfigPath}. ` +
@@ -354,11 +406,13 @@ export function formatOracleAuthConfigSummary(details: OracleConfigLoadDetails):
354
406
  const lines = [
355
407
  `Effective oracle auth config: ${details.effectiveAuthConfigPath} (agent dir: ${details.agentDir}${details.agentConfigExists ? "" : "; create this file to override auth.*"})`,
356
408
  ];
357
- if (details.projectConfigExists) {
409
+ if (details.projectConfigLoaded) {
358
410
  lines.push(
359
411
  `Project oracle config also loaded: ${details.projectConfigPath} ` +
360
412
  `(project scope can override ${[...PROJECT_OVERRIDE_KEYS].join("/")} only; auth.* still comes from ${details.effectiveAuthConfigPath}).`,
361
413
  );
414
+ } else if (details.projectConfigSkippedReason) {
415
+ lines.push(`Project oracle config present but not loaded: ${details.projectConfigPath}. ${details.projectConfigSkippedReason}`);
362
416
  }
363
417
  return lines.join("\n");
364
418
  }
@@ -665,9 +719,9 @@ function validateOracleConfig(value: unknown): OracleConfig {
665
719
  };
666
720
  }
667
721
 
668
- export function loadOracleConfig(cwd: string): OracleConfig {
669
- const details = getOracleConfigLoadDetails(cwd);
722
+ export function loadOracleConfig(cwd: string, options?: OracleConfigLoadOptions): OracleConfig {
723
+ const details = getOracleConfigLoadDetails(cwd, options);
670
724
  const globalConfig = readJson(details.agentConfigPath);
671
- const projectConfig = filterProjectConfig(readJson(details.projectConfigPath));
725
+ const projectConfig = details.projectConfigLoaded ? filterProjectConfig(readJson(details.projectConfigPath)) : undefined;
672
726
  return validateOracleConfig(deepMerge(deepMerge(DEFAULT_CONFIG, globalConfig), projectConfig));
673
727
  }
@@ -165,6 +165,7 @@ function getJobCountsForSession(sessionFile: string | undefined, cwd: string): {
165
165
  }
166
166
 
167
167
  function refreshOracleStatusSnapshot(snapshot: OraclePollerContextSnapshot): void {
168
+ if (!snapshot.hasUI) return;
168
169
  if (!snapshot.sessionFile) {
169
170
  snapshot.ui.setStatus("oracle", snapshot.ui.theme.fg("accent", "oracle: unavailable"));
170
171
  return;
@@ -1264,7 +1264,7 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
1264
1264
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1265
1265
  try {
1266
1266
  const projectCwd = getProjectId(ctx.cwd);
1267
- const baseConfig = loadOracleConfig(projectCwd);
1267
+ const baseConfig = loadOracleConfig(projectCwd, { projectConfigTrustCwd: ctx.cwd });
1268
1268
  const provider = normalizeOracleProvider(params.provider, baseConfig.defaults.provider, "oracle_auth");
1269
1269
  const message = await runOracleAuthBootstrap(authWorkerPath, projectCwd, provider);
1270
1270
  return {
@@ -1311,7 +1311,7 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
1311
1311
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1312
1312
  try {
1313
1313
  const projectCwd = getProjectId(ctx.cwd);
1314
- const baseConfig = loadOracleConfig(projectCwd);
1314
+ const baseConfig = loadOracleConfig(projectCwd, { projectConfigTrustCwd: ctx.cwd });
1315
1315
  const originSessionFile = requirePersistedSessionFile(getSessionFile(ctx), "submit oracle jobs");
1316
1316
  const projectId = getProjectId(projectCwd);
1317
1317
  const sessionId = getSessionId(originSessionFile, projectId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-oracle",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "ChatGPT and Grok web-oracle extension for pi with isolated browser auth, async jobs, and project-context archives.",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  ]
48
48
  },
49
49
  "scripts": {
50
- "check:oracle-extension": "node --check extensions/oracle/shared/browser-profile-helpers.mjs && 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",
50
+ "check:oracle-extension": "node --check extensions/oracle/shared/browser-profile-helpers.mjs && 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:typebox --outfile=/tmp/pi-oracle-extension-check.js",
51
51
  "typecheck": "tsc --noEmit -p tsconfig.json",
52
52
  "typecheck:worker-helpers": "tsc --noEmit -p tsconfig.worker-helpers.json",
53
53
  "sanity:oracle": "node scripts/oracle-sanity-runner.mjs",
@@ -83,9 +83,9 @@
83
83
  "protobufjs": "7.6.1"
84
84
  },
85
85
  "devDependencies": {
86
- "@earendil-works/pi-ai": "^0.77.0",
87
- "@earendil-works/pi-coding-agent": "^0.77.0",
88
- "@types/node": "^25.9.1",
86
+ "@earendil-works/pi-ai": "^0.79.0",
87
+ "@earendil-works/pi-coding-agent": "^0.79.0",
88
+ "@types/node": "^22.19.19",
89
89
  "esbuild": "^0.28.0",
90
90
  "tsx": "^4.22.3",
91
91
  "typebox": "^1.1.39",
@@ -18,14 +18,18 @@ export default {
18
18
  "node scripts/platform-smoke.mjs run --target <target> --suite <suite>",
19
19
  ],
20
20
  },
21
- release: {
22
- description: "Doctor-first packed-install macOS/Ubuntu/Windows release proof.",
21
+ platformMatrix: {
22
+ description: "Doctor-first packed-install macOS/Ubuntu/Windows platform proof.",
23
23
  commands: ["npm run smoke:platform:all"],
24
24
  },
25
+ release: {
26
+ description: "Full release gate: local verification plus the doctor-first platform matrix.",
27
+ commands: ["npm run release:check"],
28
+ },
25
29
  },
26
30
  requiredCrabbox: {
27
31
  source: "https://github.com/openclaw/crabbox",
28
- minVersion: "0.24.0",
32
+ minVersion: "0.26.0",
29
33
  },
30
34
  ubuntuContainerImage: "pi-oracle-platform-smoke:node24",
31
35
  ubuntuContainerBaseImage: "cimg/node:24.16",
@@ -52,6 +56,9 @@ export default {
52
56
  ai_gateway: ["AI_GATEWAY_API_KEY"],
53
57
  mistral: ["MISTRAL_API_KEY"],
54
58
  minimax: ["MINIMAX_API_KEY"],
59
+ "minimax-cn": ["MINIMAX_CN_API_KEY"],
60
+ "ant-ling": ["ANT_LING_API_KEY"],
61
+ nvidia: ["NVIDIA_API_KEY"],
55
62
  moonshot: ["MOONSHOT_API_KEY"],
56
63
  kimi: ["KIMI_API_KEY"],
57
64
  },
@@ -22,7 +22,7 @@ function usage() {
22
22
  console.log(`Usage: node scripts/oracle-real-smoke.mjs <doctor|run> [--mode packed|source]
23
23
 
24
24
  Modes:
25
- packed Release proof. npm pack -> clean pi project -> npm install tarball -> pi install -l -> run through installed package. Default.
25
+ packed Release proof. npm pack -> clean pi project -> npm install tarball -> pi install -l --approve -> run through installed package. Default.
26
26
  source Inner-loop/debug only. Loads this checkout with pi --no-extensions -e extensions/oracle/index.ts.
27
27
 
28
28
  Environment:
@@ -85,6 +85,9 @@ function apiKeyNameForProvider(provider) {
85
85
  ai_gateway: "AI_GATEWAY_API_KEY",
86
86
  mistral: "MISTRAL_API_KEY",
87
87
  minimax: "MINIMAX_API_KEY",
88
+ "minimax-cn": "MINIMAX_CN_API_KEY",
89
+ "ant-ling": "ANT_LING_API_KEY",
90
+ nvidia: "NVIDIA_API_KEY",
88
91
  moonshot: "MOONSHOT_API_KEY",
89
92
  opencode: "OPENCODE_API_KEY",
90
93
  kimi: "KIMI_API_KEY",
@@ -254,12 +257,12 @@ async function preparePackedProject({ runDir, provider, model, timeoutMs }) {
254
257
 
255
258
  await mustRun(installDir, "npm-init", npm, ["init", "-y"], { cwd: piProject, env: process.env, timeoutMs: 60_000 });
256
259
  await mustRun(installDir, "packed-node-install", npm, ["install", "--no-save", tarballPath], { cwd: piProject, env: process.env, timeoutMs: 120_000 });
257
- await mustRun(installDir, "pi-install", piCommand(), ["install", "-l", `./node_modules/${PACKAGE_NAME}`], {
260
+ await mustRun(installDir, "pi-install", piCommand(), ["install", "-l", `./node_modules/${PACKAGE_NAME}`, "--approve"], {
258
261
  cwd: piProject,
259
262
  env: { ...process.env, PI_OFFLINE: "1" },
260
263
  timeoutMs: 120_000,
261
264
  });
262
- const piList = await mustRun(installDir, "pi-list", piCommand(), ["list"], {
265
+ const piList = await mustRun(installDir, "pi-list", piCommand(), ["list", "--approve"], {
263
266
  cwd: piProject,
264
267
  env: { ...process.env, PI_OFFLINE: "1" },
265
268
  timeoutMs: 60_000,
@@ -339,6 +342,7 @@ console.log(JSON.stringify(result, null, 2));
339
342
  async function runPiAgent({ prepared, agentDir, sessionDir, jobsDir, prompt, outDir, label, timeoutMs, stopAfterJobArchive = false }) {
340
343
  mkdirSync(outDir, { recursive: true });
341
344
  const args = [
345
+ "--approve",
342
346
  "--print",
343
347
  "--provider", prepared.provider,
344
348
  "--model", prepared.model,
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Thin Crabbox CLI wrapper for pi-oracle's Ubuntu platform smoke target.
2
+ * Thin Crabbox CLI wrapper for pi-oracle's local platform smoke targets.
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
6
 
7
- const CRABBOX_BIN = process.env.PI_ORACLE_SMOKE_CRABBOX || "crabbox";
7
+ const CRABBOX_BIN = process.env.PI_ORACLE_SMOKE_CRABBOX || process.env.PLATFORM_SMOKE_CRABBOX || "crabbox";
8
8
 
9
9
  function env(name) {
10
10
  return process.env[name] ?? "";
@@ -58,9 +58,9 @@ export function execCrabbox(args, opts = {}) {
58
58
  export function buildTargetBaseArgs(targetName, config = {}) {
59
59
  switch (targetName) {
60
60
  case "macos": {
61
- const host = env("PI_ORACLE_SMOKE_MAC_HOST") || "localhost";
62
- const user = env("PI_ORACLE_SMOKE_MAC_USER") || env("USER");
63
- const workRoot = env("PI_ORACLE_SMOKE_MAC_WORK_ROOT") || `/Users/${env("USER")}/crabbox/pi-oracle`;
61
+ const host = env("PI_ORACLE_SMOKE_MAC_HOST") || env("PLATFORM_SMOKE_MAC_HOST") || "localhost";
62
+ const user = env("PI_ORACLE_SMOKE_MAC_USER") || env("PLATFORM_SMOKE_MAC_USER") || env("USER");
63
+ const workRoot = env("PI_ORACLE_SMOKE_MAC_WORK_ROOT") || env("PLATFORM_SMOKE_MAC_WORK_ROOT") || `/Users/${env("USER")}/crabbox/${config.packageName ?? "pi-oracle"}`;
64
64
  return [
65
65
  "--provider", "ssh",
66
66
  "--target", "macos",
@@ -81,7 +81,7 @@ export function buildTargetBaseArgs(targetName, config = {}) {
81
81
  case "windows-native": {
82
82
  const vm = env("PI_ORACLE_SMOKE_WINDOWS_VM") || env("PLATFORM_SMOKE_WINDOWS_VM") || config.windowsParallels?.sourceVm || "pi-extension-windows-template";
83
83
  const snapshot = env("PI_ORACLE_SMOKE_WINDOWS_SNAPSHOT") || env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || config.windowsParallels?.snapshot || "crabbox-ready";
84
- const user = env("PI_ORACLE_SMOKE_WINDOWS_USER") || env("USER");
84
+ const user = env("PI_ORACLE_SMOKE_WINDOWS_USER") || env("PLATFORM_SMOKE_WINDOWS_USER") || env("USER");
85
85
  const workRoot = env("PI_ORACLE_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || env("PLATFORM_SMOKE_WINDOWS_WORK_ROOT") || `C:\\crabbox\\${config.packageName ?? "pi-oracle"}`;
86
86
  return [
87
87
  "--provider", "parallels",
@@ -17,6 +17,15 @@ function runNode(args) {
17
17
  return spawnSync(process.execPath, args, { cwd: repoRoot, encoding: "utf8" });
18
18
  }
19
19
 
20
+ function testHelpTextIncludesTargetsAndExamples() {
21
+ const result = runNode(["scripts/platform-smoke.mjs", "--help"]);
22
+ assert.equal(result.status, 0, `help should exit cleanly: ${result.stderr}`);
23
+ assert.match(result.stdout, /Supported: macos,ubuntu,windows-native/, "help should list supported targets");
24
+ assert.match(result.stdout, /--suite\s+Suite name\. Supported: platform-build,real-extension/, "help should list supported suites");
25
+ assert.match(result.stdout, /npm run release:check/, "help should name the full release gate");
26
+ assert.match(result.stdout, /PLATFORM_SMOKE_CRABBOX/, "help should document reusable platform-smoke env knobs");
27
+ }
28
+
20
29
  function testTargetSelection() {
21
30
  const result = runNode(["scripts/platform-smoke.mjs", "run", "--target", "not-a-target", "--suite", "platform-build"]);
22
31
  assert.notEqual(result.status, 0, "unsupported targets should fail before Crabbox runs");
@@ -79,9 +88,15 @@ function testCanonicalWorkflowConfig() {
79
88
  assert.deepEqual(config.workflows?.everyday?.commands, ["npm run verify:oracle"], "everyday workflow should use the local verification gate");
80
89
  assert(config.workflows?.platformSensitive?.commands?.includes("npm run smoke:platform:doctor"), "platform-sensitive workflow should start with doctor");
81
90
  assert(config.workflows?.platformSensitive?.commands?.some((command) => command.includes("--target <target> --suite <suite>")), "platform-sensitive workflow should document focused target/suite runs");
82
- assert.deepEqual(config.workflows?.release?.commands, ["npm run smoke:platform:all"], "release workflow should use the full platform matrix");
91
+ assert.deepEqual(config.workflows?.platformMatrix?.commands, ["npm run smoke:platform:all"], "platform matrix workflow should use the full target matrix");
92
+ assert.deepEqual(config.workflows?.release?.commands, ["npm run release:check"], "release workflow should use the full local-plus-platform release gate");
93
+ assert.equal(config.requiredCrabbox?.minVersion, "0.26.0", "Crabbox baseline should match the documented provider contract");
83
94
  assert.equal(pkg.scripts["smoke:platform:all"], "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native", "full platform smoke should remain doctor-first and cover all required targets");
84
95
  assert.match(pkg.scripts["release:check"], /npm run verify:oracle && npm run smoke:platform:all/, "release check should combine local verification and full platform smoke");
96
+ const runnerSource = readFileSync(new URL("./crabbox-runner.mjs", import.meta.url), "utf8");
97
+ assert.match(runnerSource, /PLATFORM_SMOKE_CRABBOX/, "runner should honor reusable Crabbox binary override");
98
+ assert.match(runnerSource, /PLATFORM_SMOKE_MAC_WORK_ROOT/, "runner should honor reusable macOS work-root override");
99
+ assert.match(runnerSource, /PLATFORM_SMOKE_WINDOWS_WORK_ROOT/, "runner should honor reusable Windows work-root override");
85
100
  }
86
101
 
87
102
  function testRealSmokeExpensiveAgentPathsAreOptIn() {
@@ -96,6 +111,7 @@ function testRealSmokeExpensiveAgentPathsAreOptIn() {
96
111
  assert.match(source, /truthy\(env\("PI_ORACLE_REAL_TEST_NEGATIVE_SYMLINK"\)\)/, "negative symlink real-agent check should be opt-in");
97
112
  }
98
113
 
114
+ testHelpTextIncludesTargetsAndExamples();
99
115
  testTargetSelection();
100
116
  testPackedInstallCommandRendering();
101
117
  testRealExtensionPackedInstallRendering();
@@ -122,7 +122,7 @@ if ($PackTarball -and $PiCli -and (Test-Path -LiteralPath $TarballPath)) {
122
122
  if ($PACKED_NODE_INSTALL_EXIT -eq 0) {
123
123
  $PreviousPiOffline = $env:PI_OFFLINE
124
124
  $env:PI_OFFLINE = "1"
125
- & $PiCli install -l (Join-Path ".\node_modules" $PackageName) 1> $PiInstallOut 2> $PiInstallErr
125
+ & $PiCli install -l (Join-Path ".\node_modules" $PackageName) --approve 1> $PiInstallOut 2> $PiInstallErr
126
126
  $PI_INSTALL_EXIT = Exit-CodeFromLastCommand
127
127
  if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
128
128
  } else {
@@ -148,7 +148,7 @@ if ($PiCli) {
148
148
  Push-Location $PiProject
149
149
  $PreviousPiOffline = $env:PI_OFFLINE
150
150
  $env:PI_OFFLINE = "1"
151
- & $PiCli list 1> $PiListOut 2> $PiListErr
151
+ & $PiCli list --approve 1> $PiListOut 2> $PiListErr
152
152
  $PI_LIST_EXIT = Exit-CodeFromLastCommand
153
153
  if ($null -eq $PreviousPiOffline) { Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue } else { $env:PI_OFFLINE = $PreviousPiOffline }
154
154
  Pop-Location
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Ubuntu platform-build suite for pi-oracle.
3
- * The suite proves the PR's package builds/tests on Linux and installs through pi's package path.
2
+ * Cross-platform smoke suites for pi-oracle.
3
+ * The suites prove the package builds, packs, installs, loads, and runs through pi's package path.
4
4
  */
5
5
 
6
6
  import { readFileSync, writeFileSync } from "node:fs";
@@ -194,13 +194,13 @@ export function buildPlatformBuildCommand(targetName = "ubuntu", packageName = "
194
194
  lines.push('echo "PLATFORM_PACKED_NODE_INSTALL_EXIT=$PACKED_NODE_INSTALL_EXIT"');
195
195
  lines.push(...posixSection("PACKED_NODE_INSTALL_STDOUT", 'cat "$PACK_DIR/packed-node-install.stdout.txt" 2>/dev/null || true'));
196
196
  lines.push(...posixSection("PACKED_NODE_INSTALL_STDERR", 'cat "$PACK_DIR/packed-node-install.stderr.txt" 2>/dev/null || true'));
197
- lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "packed npm install failed or missing pi cli" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
197
+ lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l ./node_modules/${packageName} --approve >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "packed npm install failed or missing pi cli" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
198
198
  lines.push('echo "PLATFORM_PI_INSTALL_EXIT=$PI_INSTALL_EXIT"');
199
199
  lines.push(...posixSection("PI_INSTALL_STDOUT", 'cat "$PACK_DIR/pi-install.stdout.txt" 2>/dev/null || true'));
200
200
  lines.push(...posixSection("PI_INSTALL_STDERR", 'cat "$PACK_DIR/pi-install.stderr.txt" 2>/dev/null || true'));
201
201
  lines.push("");
202
202
  lines.push('echo "=== pi list ==="');
203
- lines.push('if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi');
203
+ lines.push('if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list --approve >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi');
204
204
  lines.push('echo "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"');
205
205
  lines.push(...posixSection("PI_LIST_STDOUT", 'cat "$PACK_DIR/pi-list.stdout.txt" 2>/dev/null || true'));
206
206
  lines.push(...posixSection("PI_LIST_STDERR", 'cat "$PACK_DIR/pi-list.stderr.txt" 2>/dev/null || true'));
@@ -41,21 +41,24 @@ Examples:
41
41
  Canonical workflows:
42
42
  Everyday local iteration: npm run verify:oracle
43
43
  Platform-sensitive changes: npm run smoke:platform:doctor, then focused run --target <target> --suite <suite>
44
- Publish/release proof: npm run smoke:platform:all
44
+ Platform matrix proof: npm run smoke:platform:all
45
+ Full release gate: npm run release:check
45
46
 
46
47
  Environment:
47
- PI_ORACLE_SMOKE_CRABBOX Optional Crabbox binary override (defaults to PATH)
48
- PI_ORACLE_SMOKE_MAC_HOST macOS SSH host (default: localhost)
49
- PI_ORACLE_SMOKE_MAC_USER macOS SSH user (default: $USER)
50
- PI_ORACLE_SMOKE_MAC_WORK_ROOT macOS Crabbox work root
51
- PI_ORACLE_SMOKE_UBUNTU_IMAGE Optional local-container image override
52
- PI_ORACLE_SMOKE_WINDOWS_VM Parallels source VM (default from config)
53
- PI_ORACLE_SMOKE_WINDOWS_SNAPSHOT Parallels snapshot (default from config)
54
- PI_ORACLE_SMOKE_WINDOWS_USER Windows SSH user (default: $USER)
55
- PI_ORACLE_SMOKE_WINDOWS_NATIVE_WORK_ROOT Windows work root
48
+ PLATFORM_SMOKE_CRABBOX Reusable Crabbox binary override (defaults to PATH)
49
+ PI_ORACLE_SMOKE_CRABBOX Project-specific Crabbox binary override (wins over PLATFORM_SMOKE_CRABBOX)
50
+ PLATFORM_SMOKE_MAC_HOST macOS SSH host (default: localhost)
51
+ PLATFORM_SMOKE_MAC_USER macOS SSH user (default: $USER)
52
+ PLATFORM_SMOKE_MAC_WORK_ROOT macOS Crabbox work root
53
+ PLATFORM_SMOKE_UBUNTU_IMAGE Optional local-container image override
54
+ PLATFORM_SMOKE_WINDOWS_VM Parallels source VM (default from config)
55
+ PLATFORM_SMOKE_WINDOWS_SNAPSHOT Parallels snapshot (default from config)
56
+ PLATFORM_SMOKE_WINDOWS_USER Windows SSH user (default: $USER)
57
+ PLATFORM_SMOKE_WINDOWS_WORK_ROOT Windows work root
58
+ PI_ORACLE_SMOKE_* Project-specific aliases for the PLATFORM_SMOKE_* knobs above
56
59
  PI_ORACLE_REAL_TEST_PROVIDER Real smoke provider (default: zai)
57
60
  PI_ORACLE_REAL_TEST_MODEL Real smoke model (default: glm-5.1)
58
- ZAI_API_KEY Default real-smoke provider API key
61
+ ZAI_API_KEY Default real-smoke provider API key for optional model-agent debugging
59
62
  `);
60
63
  }
61
64