pi-oracle 0.7.14 → 0.7.16
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 +24 -0
- package/README.md +4 -2
- package/docs/ORACLE_DESIGN.md +4 -1
- package/extensions/oracle/lib/commands.ts +29 -19
- package/extensions/oracle/lib/config.ts +22 -4
- package/extensions/oracle/lib/tools.ts +52 -36
- package/package.json +6 -3
- package/prompts/oracle-followup.md +1 -0
- package/prompts/oracle.md +1 -0
- package/scripts/platform-smoke/Dockerfile.ubuntu +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.16 - 2026-06-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- updated the local pi development and validation baseline to `@earendil-works/*` `0.80.2`
|
|
9
|
+
- exposed packaged `/oracle` and `/oracle-followup` prompt templates through the package manifest so TUI slash completion can discover them
|
|
10
|
+
- reduced always-on `oracle_submit` prompt metadata while keeping detailed dispatch rules in the prompt templates
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- made `/oracle-*` command output visible in JSON mode through a displayed custom message instead of UI notifications only
|
|
14
|
+
- deferred Chrome user-agent probing until oracle config is loaded, with a timeout on the Chrome version probe
|
|
15
|
+
|
|
16
|
+
## 0.7.15 - 2026-06-23
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- updated the local pi development and validation baseline to `@earendil-works/*` `0.80.1`
|
|
20
|
+
- refreshed oracle README and sanity contracts for pi `0.80.1`
|
|
21
|
+
- moved script-only `@earendil-works/pi-ai` type imports to `@earendil-works/pi-ai/compat`, matching the Pi 0.80 source typechecking migration guidance
|
|
22
|
+
|
|
23
|
+
### Compatibility
|
|
24
|
+
- reviewed the pi `0.80.0` and `0.80.1` changelogs plus current extension lifecycle, project-trust, security, and package docs; no oracle runtime behavior change was required
|
|
25
|
+
|
|
26
|
+
### Validation
|
|
27
|
+
- ran `npm run release:check`, including `npm run verify:oracle`, fresh live ChatGPT preset proof for every canonical preset, and Crabbox macOS, Ubuntu, and Windows native `platform-build` plus `real-extension` suites
|
|
28
|
+
|
|
5
29
|
## 0.7.14 - 2026-06-22
|
|
6
30
|
|
|
7
31
|
### 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.
|
|
5
|
+
> Status: experimental public beta. Current local validation baseline is pi `0.80.2`; the platform-smoke harness covers macOS, Linux, and Windows native with Chromium-family browsers. Pi `0.80.2+` 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
|
|
|
@@ -71,13 +71,15 @@ Or from GitHub:
|
|
|
71
71
|
pi install https://github.com/fitchmultz/pi-oracle
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
To update the package later, use `pi update --extensions`, `pi update --all`, or `pi update npm:pi-oracle`. Bare `pi update` updates pi itself only. Versioned npm/git refs stay pinned until you change the configured source.
|
|
75
|
+
|
|
74
76
|
### 2. Check requirements
|
|
75
77
|
|
|
76
78
|
You need:
|
|
77
79
|
|
|
78
80
|
- macOS, Linux, or Windows native
|
|
79
81
|
- Node.js 22 or newer
|
|
80
|
-
- Suggested tested floor: `pi` 0.
|
|
82
|
+
- Suggested tested floor: `pi` 0.80.2 or newer; older pi versions are not blocked by package metadata but are outside the current validation baseline
|
|
81
83
|
- Google Chrome/Chromium or another Chromium-family browser
|
|
82
84
|
- ChatGPT or Grok already signed in to the configured local browser profile for the provider you plan to use
|
|
83
85
|
- `agent-browser` and `tar` available on the machine; `zstd` is also required when submitting ChatGPT `.tar.zst` archives
|
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.
|
|
10
|
+
- `pi` 0.80.2+ 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
|
|
|
@@ -639,6 +639,9 @@ Remaining non-blocking hardening work:
|
|
|
639
639
|
- keep hardening model-selection verification against future ChatGPT UI variation
|
|
640
640
|
|
|
641
641
|
Recent proof points:
|
|
642
|
+
- Pi 0.80.2 local gate: `npm run verify:oracle` passed on 2026-06-24 after the JSON command output, prompt-manifest, schema, and lazy Chrome-probe audit fixes
|
|
643
|
+
- Pi 0.80.2 isolated extension smokes: `.artifacts/real-smoke/run-1782321054924-jnq0x3` passed source proof, and `.artifacts/real-smoke/run-1782321056224-yuq5a2` passed packed-install proof
|
|
644
|
+
- Pi 0.80.2 JSON command smoke: `pi --no-extensions -e ./extensions/oracle/index.ts --mode json --no-session --no-approve "/oracle-status"` emitted displayed `oracle-command-output` JSON events
|
|
642
645
|
- Pi 0.79.10 local gate: `npm run verify:oracle` passed on 2026-06-22 after the 0.79.10 baseline refresh and `CONFIG_DIR_NAME` cleanup
|
|
643
646
|
- Pi 0.79.10 isolated extension smokes: `.artifacts/real-smoke/run-1782137209549-0xe67z` passed packed-install proof, and `.artifacts/real-smoke/run-1782137217821-95a1po` passed source model-agent proof
|
|
644
647
|
- Pi 0.79.10 platform artifacts: `.artifacts/platform-smoke/run-1782137574391-7lay68` (macOS platform-build), `.artifacts/platform-smoke/run-1782137619352-gku7jz` (macOS real-extension), `.artifacts/platform-smoke/run-1782137587082-d7kg4p` (Ubuntu platform-build), `.artifacts/platform-smoke/run-1782137619176-lgxezy` (Ubuntu real-extension), `.artifacts/platform-smoke/run-1782137625964-66z0oc` (Windows native platform-build), `.artifacts/platform-smoke/run-1782137752969-pbmdj1` (Windows native real-extension)
|
|
@@ -83,12 +83,21 @@ function isProjectTrusted(ctx: ExtensionCommandContext): boolean {
|
|
|
83
83
|
return (ctx as { isProjectTrusted?: () => boolean }).isProjectTrusted?.() ?? true;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function emitCommandOutput(ctx: ExtensionCommandContext, message: string, level: "info" | "warning" | "error" = "info"): void {
|
|
86
|
+
function emitCommandOutput(pi: ExtensionAPI, ctx: ExtensionCommandContext, message: string, level: "info" | "warning" | "error" = "info"): void {
|
|
87
87
|
if (ctx.mode === "print") {
|
|
88
88
|
process.stdout.write(`${message}\n`);
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
-
ctx.
|
|
91
|
+
if (ctx.hasUI) {
|
|
92
|
+
ctx.ui.notify(message, level);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
pi.sendMessage({
|
|
96
|
+
customType: "oracle-command-output",
|
|
97
|
+
content: message,
|
|
98
|
+
display: true,
|
|
99
|
+
details: { level },
|
|
100
|
+
});
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string, workerPath: string, _promptTemplates: OracleCommandPromptTemplates = {}): void {
|
|
@@ -98,11 +107,11 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
98
107
|
try {
|
|
99
108
|
const provider = parseOracleAuthProvider(args);
|
|
100
109
|
const providerLabel = provider === "grok" ? "Grok" : provider === "chatgpt" ? "ChatGPT" : "configured provider";
|
|
101
|
-
emitCommandOutput(ctx, `Syncing ${providerLabel} cookies from the configured local browser profile into the oracle auth seed profile…`, "info");
|
|
110
|
+
emitCommandOutput(pi, ctx, `Syncing ${providerLabel} cookies from the configured local browser profile into the oracle auth seed profile…`, "info");
|
|
102
111
|
const result = await runOracleAuthBootstrap(authWorkerPath, ctx.cwd, provider, { projectConfigTrusted: isProjectTrusted(ctx) });
|
|
103
|
-
emitCommandOutput(ctx, result, "info");
|
|
112
|
+
emitCommandOutput(pi, ctx, result, "info");
|
|
104
113
|
} catch (error) {
|
|
105
|
-
emitCommandOutput(ctx, error instanceof Error ? error.message : String(error), "warning");
|
|
114
|
+
emitCommandOutput(pi, ctx, error instanceof Error ? error.message : String(error), "warning");
|
|
106
115
|
}
|
|
107
116
|
},
|
|
108
117
|
});
|
|
@@ -113,12 +122,12 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
113
122
|
const explicitJobId = args.trim();
|
|
114
123
|
const jobId = explicitJobId || getLatestJobId(ctx.cwd);
|
|
115
124
|
if (!jobId) {
|
|
116
|
-
emitCommandOutput(ctx, "No oracle jobs found for this project", "info");
|
|
125
|
+
emitCommandOutput(pi, ctx, "No oracle jobs found for this project", "info");
|
|
117
126
|
return;
|
|
118
127
|
}
|
|
119
128
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
120
129
|
if (!job) {
|
|
121
|
-
emitCommandOutput(ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
130
|
+
emitCommandOutput(pi, ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
122
131
|
return;
|
|
123
132
|
}
|
|
124
133
|
if (isTerminalOracleJob(job)) {
|
|
@@ -130,7 +139,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
130
139
|
}
|
|
131
140
|
const summary = await summarizeJob(job.id);
|
|
132
141
|
const recentJobs = !explicitJobId ? listRecentJobIds(ctx.cwd) : undefined;
|
|
133
|
-
emitCommandOutput(ctx, [summary, recentJobs ? `Recent jobs: ${recentJobs}` : undefined].filter(Boolean).join("\n"), "info");
|
|
142
|
+
emitCommandOutput(pi, ctx, [summary, recentJobs ? `Recent jobs: ${recentJobs}` : undefined].filter(Boolean).join("\n"), "info");
|
|
134
143
|
},
|
|
135
144
|
});
|
|
136
145
|
|
|
@@ -140,12 +149,12 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
140
149
|
const explicitJobId = args.trim();
|
|
141
150
|
const jobId = explicitJobId || getLatestJobId(ctx.cwd);
|
|
142
151
|
if (!jobId) {
|
|
143
|
-
emitCommandOutput(ctx, "No oracle jobs found for this project", "info");
|
|
152
|
+
emitCommandOutput(pi, ctx, "No oracle jobs found for this project", "info");
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
146
155
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
147
156
|
if (!job) {
|
|
148
|
-
emitCommandOutput(ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
157
|
+
emitCommandOutput(pi, ctx, `Oracle job ${jobId} was not found in this project`, "warning");
|
|
149
158
|
return;
|
|
150
159
|
}
|
|
151
160
|
if (isTerminalOracleJob(job)) {
|
|
@@ -155,7 +164,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
155
164
|
cwd: ctx.cwd,
|
|
156
165
|
});
|
|
157
166
|
}
|
|
158
|
-
emitCommandOutput(ctx, await summarizeJob(job.id, { responsePreview: true }), "info");
|
|
167
|
+
emitCommandOutput(pi, ctx, await summarizeJob(job.id, { responsePreview: true }), "info");
|
|
159
168
|
},
|
|
160
169
|
});
|
|
161
170
|
|
|
@@ -164,20 +173,20 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
164
173
|
handler: async (args, ctx) => {
|
|
165
174
|
const jobId = args.trim();
|
|
166
175
|
if (!jobId) {
|
|
167
|
-
emitCommandOutput(ctx, "Usage: /oracle-cancel <job-id>\nUse /oracle-status to find the job id you want to cancel.", "warning");
|
|
176
|
+
emitCommandOutput(pi, ctx, "Usage: /oracle-cancel <job-id>\nUse /oracle-status to find the job id you want to cancel.", "warning");
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
const job = readScopedJob(jobId, ctx.cwd);
|
|
172
181
|
if (!job) {
|
|
173
|
-
emitCommandOutput(ctx, `Oracle job ${jobId} not found in this project`, "warning");
|
|
182
|
+
emitCommandOutput(pi, ctx, `Oracle job ${jobId} not found in this project`, "warning");
|
|
174
183
|
return;
|
|
175
184
|
}
|
|
176
185
|
if (!isOpenOracleJob(job)) {
|
|
177
186
|
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");
|
|
187
|
+
emitCommandOutput(pi, ctx, `Job is already terminal: ${job.status}. Use /oracle-read ${job.id} for details or /oracle-clean ${job.id} to remove it.`, "info");
|
|
179
188
|
} else {
|
|
180
|
-
emitCommandOutput(ctx, `Oracle job ${jobId} is not cancellable (${job.status})`, "info");
|
|
189
|
+
emitCommandOutput(pi, ctx, `Oracle job ${jobId} is not cancellable (${job.status})`, "info");
|
|
181
190
|
}
|
|
182
191
|
return;
|
|
183
192
|
}
|
|
@@ -187,7 +196,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
187
196
|
await promoteQueuedJobs({ workerPath, source: "oracle_cancel_command" });
|
|
188
197
|
}
|
|
189
198
|
refreshOracleStatus(ctx);
|
|
190
|
-
emitCommandOutput(ctx, formatOracleCancelOutcome(cancelled), "info");
|
|
199
|
+
emitCommandOutput(pi, ctx, formatOracleCancelOutcome(cancelled), "info");
|
|
191
200
|
},
|
|
192
201
|
});
|
|
193
202
|
|
|
@@ -196,19 +205,20 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
196
205
|
handler: async (args, ctx: ExtensionCommandContext) => {
|
|
197
206
|
const target = args.trim();
|
|
198
207
|
if (!target) {
|
|
199
|
-
emitCommandOutput(ctx, "Usage: /oracle-clean <job-id|all>", "warning");
|
|
208
|
+
emitCommandOutput(pi, ctx, "Usage: /oracle-clean <job-id|all>", "warning");
|
|
200
209
|
return;
|
|
201
210
|
}
|
|
202
211
|
|
|
203
212
|
const jobs = target === "all" ? listJobsForCwd(ctx.cwd) : [readScopedJob(target, ctx.cwd)].filter(Boolean);
|
|
204
213
|
if (jobs.length === 0) {
|
|
205
|
-
emitCommandOutput(ctx, "No matching oracle jobs found", "warning");
|
|
214
|
+
emitCommandOutput(pi, ctx, "No matching oracle jobs found", "warning");
|
|
206
215
|
return;
|
|
207
216
|
}
|
|
208
217
|
|
|
209
218
|
const nonTerminalJobs = jobs.filter((job): job is NonNullable<typeof job> => Boolean(job && !isTerminalOracleJob(job)));
|
|
210
219
|
if (nonTerminalJobs.length > 0) {
|
|
211
220
|
emitCommandOutput(
|
|
221
|
+
pi,
|
|
212
222
|
ctx,
|
|
213
223
|
`Refusing to remove non-terminal oracle job${nonTerminalJobs.length === 1 ? "" : "s"}: ${nonTerminalJobs.map((job) => job.id).join(", ")}`,
|
|
214
224
|
"warning",
|
|
@@ -246,7 +256,7 @@ export function registerOracleCommands(pi: ExtensionAPI, authWorkerPath: string,
|
|
|
246
256
|
: removedCount === jobs.length
|
|
247
257
|
? `Removed ${removedCount} oracle job director${removedCount === 1 ? "y" : "ies"}.`
|
|
248
258
|
: `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");
|
|
259
|
+
emitCommandOutput(pi, ctx, `${removalSummary}${warningSuffix}`, cleanupWarnings.length > 0 ? "warning" : "info");
|
|
250
260
|
},
|
|
251
261
|
});
|
|
252
262
|
}
|
|
@@ -308,7 +308,7 @@ function detectDefaultChromeUserAgent(executablePath: string | undefined): strin
|
|
|
308
308
|
const platformToken = chromeUserAgentPlatformToken(process.platform);
|
|
309
309
|
if (!platformToken) return undefined;
|
|
310
310
|
try {
|
|
311
|
-
const versionOutput = execFileSync(executablePath, ["--version"], { encoding: "utf8", env: sweetCookieSafeStoragePasswordScrubbedEnv() }).trim();
|
|
311
|
+
const versionOutput = execFileSync(executablePath, ["--version"], { encoding: "utf8", env: sweetCookieSafeStoragePasswordScrubbedEnv(), timeout: 1000 }).trim();
|
|
312
312
|
const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+\.\d+)/);
|
|
313
313
|
if (!versionMatch) return undefined;
|
|
314
314
|
return `Mozilla/5.0 (${platformToken}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${versionMatch[1]} Safari/537.36`;
|
|
@@ -318,8 +318,17 @@ function detectDefaultChromeUserAgent(executablePath: string | undefined): strin
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
const detectedChromeExecutablePath = detectDefaultChromeExecutablePath();
|
|
321
|
-
|
|
321
|
+
let detectedChromeUserAgent: string | undefined;
|
|
322
|
+
let detectedChromeUserAgentResolved = false;
|
|
322
323
|
const agentExtensionsDir = join(getAgentDir(), "extensions");
|
|
324
|
+
|
|
325
|
+
function getDetectedChromeUserAgent(): string | undefined {
|
|
326
|
+
if (!detectedChromeUserAgentResolved) {
|
|
327
|
+
detectedChromeUserAgent = detectDefaultChromeUserAgent(detectedChromeExecutablePath);
|
|
328
|
+
detectedChromeUserAgentResolved = true;
|
|
329
|
+
}
|
|
330
|
+
return detectedChromeUserAgent;
|
|
331
|
+
}
|
|
323
332
|
const detectedChromeProfileName = detectDefaultBrowserProfileSource(process.platform);
|
|
324
333
|
|
|
325
334
|
export interface OracleConfigLoadOptions {
|
|
@@ -442,7 +451,7 @@ export const DEFAULT_CONFIG: OracleConfig = {
|
|
|
442
451
|
authUrl: "https://chatgpt.com/auth/login",
|
|
443
452
|
runMode: "headless",
|
|
444
453
|
executablePath: detectedChromeExecutablePath,
|
|
445
|
-
userAgent:
|
|
454
|
+
userAgent: undefined,
|
|
446
455
|
args: ["--disable-blink-features=AutomationControlled"],
|
|
447
456
|
},
|
|
448
457
|
auth: {
|
|
@@ -732,5 +741,14 @@ export function loadOracleConfig(cwd: string, options?: OracleConfigLoadOptions)
|
|
|
732
741
|
const details = getOracleConfigLoadDetails(cwd, options);
|
|
733
742
|
const globalConfig = readJson(details.agentConfigPath);
|
|
734
743
|
const projectConfig = details.projectConfigLoaded ? filterProjectConfig(readJson(details.projectConfigPath)) : undefined;
|
|
735
|
-
|
|
744
|
+
const globalBrowser = isObject(globalConfig) ? globalConfig.browser : undefined;
|
|
745
|
+
const hasConfiguredUserAgent = isObject(globalBrowser) && globalBrowser.userAgent !== undefined;
|
|
746
|
+
const defaults = {
|
|
747
|
+
...DEFAULT_CONFIG,
|
|
748
|
+
browser: {
|
|
749
|
+
...DEFAULT_CONFIG.browser,
|
|
750
|
+
userAgent: hasConfiguredUserAgent ? undefined : getDetectedChromeUserAgent(),
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
return validateOracleConfig(deepMerge(deepMerge(defaults, globalConfig), projectConfig));
|
|
736
754
|
}
|
|
@@ -10,13 +10,14 @@ import { join } from "node:path";
|
|
|
10
10
|
import { createArchive, type ArchiveCreationResult, type ArchiveSizeBreakdownRow } from "./archive.js";
|
|
11
11
|
import { runOracleAuthBootstrap } from "./auth.js";
|
|
12
12
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { Type } from "typebox";
|
|
13
|
+
import { Type, type Static } from "typebox";
|
|
14
14
|
import { formatOracleCancelOutcome, formatOracleJobSummary, formatOracleSubmitResponse } from "../shared/job-observability-helpers.mjs";
|
|
15
15
|
import { getLatestOracleJobLifecycleEvent, getLatestOracleTerminalLifecycleEvent, transitionOracleJobPhase } from "../shared/job-lifecycle-helpers.mjs";
|
|
16
16
|
import { isLockTimeoutError, withGlobalReconcileLock, withLock } from "./locks.js";
|
|
17
17
|
import {
|
|
18
18
|
coerceOracleSubmitPresetId,
|
|
19
19
|
loadOracleConfig,
|
|
20
|
+
GROK_MODES,
|
|
20
21
|
ORACLE_PROVIDERS,
|
|
21
22
|
ORACLE_SUBMIT_PRESET_IDS,
|
|
22
23
|
resolveOracleConfigForProvider,
|
|
@@ -64,6 +65,16 @@ import {
|
|
|
64
65
|
tryAcquireRuntimeLease,
|
|
65
66
|
} from "./runtime.js";
|
|
66
67
|
|
|
68
|
+
const ORACLE_PROVIDER_PARAM_DESCRIPTION = `Oracle web provider. Omit to use the configured default provider. Supported providers: ${ORACLE_PROVIDERS.join(", ")}.`;
|
|
69
|
+
const ORACLE_PROVIDER_PARAM = Type.Optional(Type.String({
|
|
70
|
+
description: ORACLE_PROVIDER_PARAM_DESCRIPTION,
|
|
71
|
+
enum: [...ORACLE_PROVIDERS],
|
|
72
|
+
}));
|
|
73
|
+
const ORACLE_GROK_MODE_PARAM = Type.Optional(Type.String({
|
|
74
|
+
description: "Provider mode. For Grok, only heavy is currently supported. Omit to use the configured default mode.",
|
|
75
|
+
enum: [...GROK_MODES],
|
|
76
|
+
}));
|
|
77
|
+
|
|
67
78
|
const ORACLE_SUBMIT_PARAMS = Type.Object({
|
|
68
79
|
prompt: Type.String({ description: "Prompt text to send to ChatGPT or Grok web." }),
|
|
69
80
|
files: Type.Array(Type.String({
|
|
@@ -74,11 +85,7 @@ const ORACLE_SUBMIT_PARAMS = Type.Object({
|
|
|
74
85
|
description: "Exact project-relative files/directories to include in the oracle archive.",
|
|
75
86
|
minItems: 1,
|
|
76
87
|
}),
|
|
77
|
-
provider:
|
|
78
|
-
Type.String({
|
|
79
|
-
description: `Oracle web provider. Omit to use the configured default provider. Supported providers: ${ORACLE_PROVIDERS.join(", ")}. Use grok when the user asks to oracle to Grok. Grok archives are capped at 200 MiB.`,
|
|
80
|
-
}),
|
|
81
|
-
),
|
|
88
|
+
provider: ORACLE_PROVIDER_PARAM,
|
|
82
89
|
preset: Type.Optional(
|
|
83
90
|
Type.String({
|
|
84
91
|
description:
|
|
@@ -86,40 +93,36 @@ const ORACLE_SUBMIT_PARAMS = Type.Object({
|
|
|
86
93
|
"Matching human-readable preset labels and common hyphen/space variants are normalized automatically. Do not pass preset when provider is grok.",
|
|
87
94
|
}),
|
|
88
95
|
),
|
|
89
|
-
mode:
|
|
90
|
-
Type.String({
|
|
91
|
-
description: "Provider mode. For Grok, only heavy is currently supported. Omit to use the configured default mode.",
|
|
92
|
-
}),
|
|
93
|
-
),
|
|
96
|
+
mode: ORACLE_GROK_MODE_PARAM,
|
|
94
97
|
followUpJobId: Type.Optional(Type.String({ description: "Earlier oracle job id whose chat thread should be continued." })),
|
|
95
98
|
chatGptConversationId: Type.Optional(Type.String({
|
|
96
99
|
description: "Existing ChatGPT conversation id, or full https://chatgpt.com/c/... URL, to continue. Omit for default behavior: starting a fresh oracle thread. Do not combine with followUpJobId.",
|
|
97
100
|
minLength: 1,
|
|
98
101
|
pattern: "^.*\\S.*$",
|
|
99
102
|
})),
|
|
100
|
-
});
|
|
103
|
+
}, { additionalProperties: false });
|
|
101
104
|
|
|
102
105
|
const ORACLE_PREFLIGHT_PARAMS = Type.Object({
|
|
103
|
-
provider:
|
|
106
|
+
provider: ORACLE_PROVIDER_PARAM,
|
|
104
107
|
followUpJobId: Type.Optional(Type.String({ description: "Earlier oracle job id whose provider/thread readiness should be checked." })),
|
|
105
108
|
chatGptConversationId: Type.Optional(Type.String({
|
|
106
109
|
description: "Existing ChatGPT conversation id, or full https://chatgpt.com/c/... URL, whose provider/thread readiness should be checked. Do not combine with followUpJobId.",
|
|
107
110
|
minLength: 1,
|
|
108
111
|
pattern: "^.*\\S.*$",
|
|
109
112
|
})),
|
|
110
|
-
});
|
|
113
|
+
}, { additionalProperties: false });
|
|
111
114
|
|
|
112
115
|
const ORACLE_AUTH_PARAMS = Type.Object({
|
|
113
|
-
provider:
|
|
114
|
-
});
|
|
116
|
+
provider: ORACLE_PROVIDER_PARAM,
|
|
117
|
+
}, { additionalProperties: false });
|
|
115
118
|
|
|
116
119
|
const ORACLE_READ_PARAMS = Type.Object({
|
|
117
120
|
jobId: Type.String({ description: "Oracle job id." }),
|
|
118
|
-
});
|
|
121
|
+
}, { additionalProperties: false });
|
|
119
122
|
|
|
120
123
|
const ORACLE_CANCEL_PARAMS = Type.Object({
|
|
121
124
|
jobId: Type.String({ description: "Oracle job id." }),
|
|
122
|
-
});
|
|
125
|
+
}, { additionalProperties: false });
|
|
123
126
|
|
|
124
127
|
const MAX_QUEUED_JOBS_PER_ACTIVE_RUNTIME = 1;
|
|
125
128
|
const MAX_QUEUED_ARCHIVE_BYTES_PER_ACTIVE_RUNTIME = resolveOracleProviderArchivePlan("chatgpt").maxArchiveBytes;
|
|
@@ -132,6 +135,24 @@ function normalizeOracleProvider(value: unknown, fallback: OracleProvider, toolN
|
|
|
132
135
|
throw new Error(`Unknown ${toolName} provider: ${value}. Use chatgpt or grok.`);
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
function prepareOracleProviderAliases<T>(args: unknown, toolName: string): T {
|
|
139
|
+
const record = asRecord(args);
|
|
140
|
+
if (!record) return args as T;
|
|
141
|
+
return {
|
|
142
|
+
...record,
|
|
143
|
+
provider: typeof record.provider === "string" ? normalizeOracleProvider(record.provider, "chatgpt", toolName) : record.provider,
|
|
144
|
+
} as T;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function prepareOracleSubmitArguments(args: unknown): Static<typeof ORACLE_SUBMIT_PARAMS> {
|
|
148
|
+
const record = asRecord(args);
|
|
149
|
+
if (!record) return args as Static<typeof ORACLE_SUBMIT_PARAMS>;
|
|
150
|
+
return {
|
|
151
|
+
...prepareOracleProviderAliases<Record<string, unknown>>(record, "oracle_submit"),
|
|
152
|
+
mode: typeof record.mode === "string" ? normalizeGrokMode(record.mode, "heavy") : record.mode,
|
|
153
|
+
} as Static<typeof ORACLE_SUBMIT_PARAMS>;
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
function normalizeGrokMode(value: unknown, fallback: "heavy"): "heavy" {
|
|
136
157
|
if (value === undefined) return fallback;
|
|
137
158
|
if (typeof value !== "string") throw new Error("oracle_submit mode must be a string");
|
|
@@ -789,6 +810,7 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
|
|
|
789
810
|
"Call oracle_preflight before doing expensive /oracle preparation. Pass provider='grok' when the user explicitly asks for Grok, followUpJobId for same-thread follow-ups from oracle-created jobs, or chatGptConversationId when the user explicitly provides an existing ChatGPT browser conversation id/URL. If ready is false, stop immediately and report the suggested next step instead of reading files or crafting archive inputs.",
|
|
790
811
|
],
|
|
791
812
|
parameters: ORACLE_PREFLIGHT_PARAMS,
|
|
813
|
+
prepareArguments: (args) => prepareOracleProviderAliases(args, "oracle_preflight"),
|
|
792
814
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
793
815
|
const details = await runOraclePreflight(ctx, params);
|
|
794
816
|
return {
|
|
@@ -809,6 +831,7 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
|
|
|
809
831
|
"If oracle_auth itself fails, stop and report the failure instead of looping.",
|
|
810
832
|
],
|
|
811
833
|
parameters: ORACLE_AUTH_PARAMS,
|
|
834
|
+
prepareArguments: (args) => prepareOracleProviderAliases(args, "oracle_auth"),
|
|
812
835
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
813
836
|
try {
|
|
814
837
|
const projectCwd = getProjectId(ctx.cwd);
|
|
@@ -838,26 +861,16 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
|
|
|
838
861
|
"Optional thread target: pass `chatGptConversationId` only when the user explicitly provides an existing ChatGPT browser conversation id/URL to continue; omit it for the default fresh oracle thread.",
|
|
839
862
|
promptSnippet: "Dispatch a background ChatGPT or Grok web oracle job after gathering repo context.",
|
|
840
863
|
promptGuidelines: [
|
|
841
|
-
"
|
|
842
|
-
"
|
|
843
|
-
"
|
|
844
|
-
"
|
|
845
|
-
"
|
|
846
|
-
"
|
|
847
|
-
"
|
|
848
|
-
"When files='.' and the post-exclusion archive is still too large, submit automatically prunes the largest nested directories matching generic generated-output names like build/, dist/, out/, coverage/, and tmp/ outside obvious source roots like src/ and lib/ until the archive fits or no candidate remains; successful submissions report what was pruned.",
|
|
849
|
-
"If a submitted oracle job later fails because upload is rejected, retry smaller: remove the largest obviously irrelevant/generated content first, then narrow to modified files plus adjacent files plus directly relevant subtrees, then explain the cut or ask the user if still needed.",
|
|
850
|
-
"If oracle_submit fails before dispatch with details.error.code === 'archive_too_large' or an upload-limit message, treat that as retryable and retry automatically with a smaller archive.",
|
|
851
|
-
"For archive_too_large retries, use the reported top-level size summary and any auto-pruned paths to decide what to cut: first remove the largest obviously irrelevant/generated/history/export content, then if needed narrow to the directly relevant subtrees plus adjacent docs/tests/config.",
|
|
852
|
-
"Do not loop forever: stop after at most two total oracle_submit attempts for the same request, and if it still does not fit explain what was cut and why.",
|
|
853
|
-
"For any other submit-time error, stop and report the error instead of retrying automatically.",
|
|
854
|
-
"If oracle_submit returns a queued job instead of an immediately dispatched one, treat that as success and stop exactly the same way.",
|
|
855
|
-
"After a successful or queued oracle_submit, stop; do not continue the task while the oracle job is running. If oracle_submit failed with retryable archive_too_large, narrow the archive and retry first.",
|
|
856
|
-
"For ChatGPT, use `preset` as the only model-selection parameter on oracle_submit. " +
|
|
857
|
-
`Canonical ids: ${ORACLE_SUBMIT_PRESET_IDS.join(", ")}. ` +
|
|
858
|
-
"matching human-readable preset labels are normalized automatically. Omit preset to use the configured default. For Grok, pass provider='grok' and omit preset; only Heavy is supported today.",
|
|
864
|
+
"Call oracle_preflight first, then gather enough context to choose archive inputs before oracle_submit.",
|
|
865
|
+
"Use context-rich archives when they fit; use files='.' for broad repo-wide asks. Default exclusions already skip common bulky outputs and obvious credentials/private data.",
|
|
866
|
+
"Pass chatGptConversationId only when the user explicitly provides an existing ChatGPT conversation id/URL; otherwise omit it for a fresh thread.",
|
|
867
|
+
"If auth is stale, call oracle_auth at most once before retrying. If oracle_auth fails, stop and report it.",
|
|
868
|
+
"If details.error.code is 'archive_too_large', retry once with a smaller archive using the reported size/pruning details; do not loop.",
|
|
869
|
+
"After a successful or queued oracle_submit, stop and report only the dispatch summary. Do not keep working while the oracle job runs.",
|
|
870
|
+
"For ChatGPT, use preset only when the user requests model control; otherwise omit it. For Grok, pass provider='grok' and omit preset.",
|
|
859
871
|
],
|
|
860
872
|
parameters: ORACLE_SUBMIT_PARAMS,
|
|
873
|
+
prepareArguments: prepareOracleSubmitArguments,
|
|
861
874
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
862
875
|
try {
|
|
863
876
|
const projectCwd = getProjectId(ctx.cwd);
|
|
@@ -878,6 +891,9 @@ export function registerOracleTools(pi: ExtensionAPI, workerPath: string, authWo
|
|
|
878
891
|
if (provider === "grok" && typeof params.preset === "string") {
|
|
879
892
|
throw new Error("oracle_submit preset is only valid for ChatGPT. For Grok, use provider='grok' and mode='heavy'.");
|
|
880
893
|
}
|
|
894
|
+
if (provider === "chatgpt" && typeof params.mode === "string") {
|
|
895
|
+
throw new Error("oracle_submit mode is only valid for Grok. For ChatGPT, omit mode and use preset for model selection.");
|
|
896
|
+
}
|
|
881
897
|
const selection = provider === "grok"
|
|
882
898
|
? resolveOracleGrokMode(normalizeGrokMode(params.mode, baseConfig.defaults.grokMode))
|
|
883
899
|
: resolveOracleSubmitPreset(typeof params.preset === "string" ? coerceOracleSubmitPresetId(params.preset) : baseConfig.defaults.preset);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-oracle",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.16",
|
|
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",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|
|
44
44
|
"./extensions/oracle/index.ts"
|
|
45
|
+
],
|
|
46
|
+
"prompts": [
|
|
47
|
+
"./prompts"
|
|
45
48
|
]
|
|
46
49
|
},
|
|
47
50
|
"scripts": {
|
|
@@ -83,8 +86,8 @@
|
|
|
83
86
|
"protobufjs": "7.6.1"
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
86
|
-
"@earendil-works/pi-ai": "^0.
|
|
87
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
89
|
+
"@earendil-works/pi-ai": "^0.80.2",
|
|
90
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
88
91
|
"@types/node": "^22.19.19",
|
|
89
92
|
"esbuild": "^0.28.0",
|
|
90
93
|
"tsx": "^4.22.3",
|
package/prompts/oracle.md
CHANGED