omnirush 0.3.3 → 0.4.2
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/assets/collect-once.ts +77 -39
- package/assets/extensions/omnirush/auth.js +56 -17
- package/assets/extensions/omnirush/collector-lib.ts +510 -201
- package/assets/extensions/omnirush/collector.ts +10 -2
- package/assets/extensions/omnirush/commands.ts +148 -23
- package/assets/extensions/omnirush/index.ts +4 -0
- package/assets/extensions/omnirush/mcp-client.bundle.js +19432 -0
- package/assets/extensions/omnirush/mcp-lib.ts +199 -0
- package/assets/extensions/omnirush/mcp.ts +379 -0
- package/assets/extensions/omnirush/retry.js +144 -0
- package/assets/extensions/omnirush/sota-lib.ts +64 -0
- package/assets/extensions/omnirush/sota.ts +159 -28
- package/assets/extensions/omnirush/status-lib.ts +85 -0
- package/assets/extensions/omnirush/usage.ts +77 -0
- package/package.json +7 -2
- package/scripts/build-mcp-bundle.js +47 -0
- package/scripts/patch-pi-branding.js +27 -0
- package/scripts/postinstall.js +25 -8
- package/src/bin.js +299 -46
- package/src/lib.js +13 -0
|
@@ -93,6 +93,33 @@ export const REPLACEMENTS = [
|
|
|
93
93
|
from: "Update pi only (self works as alias to pi)",
|
|
94
94
|
to: "Update the agent only (self works as an alias)",
|
|
95
95
|
},
|
|
96
|
+
// Belt-and-braces against the pi.dev update banner (Foxtran's 0.73.1
|
|
97
|
+
// report: a stale core showed "Update Available — pi.dev/changelog"
|
|
98
|
+
// even though PI_OFFLINE should suppress the check). PI_OFFLINE=1 in
|
|
99
|
+
// the spawn env stops the check; these rewrites make the banner
|
|
100
|
+
// UNRENDERABLE even where PI_OFFLINE is absent (direct pi spawns,
|
|
101
|
+
// older cores): showNewVersionNotification returns before its body
|
|
102
|
+
// (the parameter is renamed so the replacement stays idempotent and
|
|
103
|
+
// never re-matches), and the pi.dev changelog URL is blanked. The
|
|
104
|
+
// parameter spelling differs between the standalone dist (readable)
|
|
105
|
+
// and the minified runtime bundle chunk, hence the two variants; a
|
|
106
|
+
// future core that renames the parameter just reports "missing"
|
|
107
|
+
// (fail-soft) and we would re-derive the target.
|
|
108
|
+
{
|
|
109
|
+
label: "TUI update banner (standalone dist)",
|
|
110
|
+
from: "showNewVersionNotification(release) {",
|
|
111
|
+
to: "showNewVersionNotification(_release) { return;",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: "TUI update banner (runtime bundle chunk)",
|
|
115
|
+
from: "showNewVersionNotification(release2){",
|
|
116
|
+
to: "showNewVersionNotification(_r){return;",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: "pi.dev changelog URL",
|
|
120
|
+
from: '"https://pi.dev/changelog"',
|
|
121
|
+
to: '""',
|
|
122
|
+
},
|
|
96
123
|
];
|
|
97
124
|
|
|
98
125
|
function findPiRoot(startDir) {
|
package/scripts/postinstall.js
CHANGED
|
@@ -145,11 +145,15 @@ function findFile(rootDir, fileName) {
|
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
export function managedBunPath() {
|
|
149
|
+
return path.join(RUNTIME, exe("bun"));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function fetchBun(plat, log = console.log) {
|
|
153
|
+
const existing = managedBunPath();
|
|
150
154
|
if (fs.existsSync(existing)) return;
|
|
151
155
|
const zipUrl = `https://github.com/oven-sh/bun/releases/download/${BUN_VERSION}/bun-${bunAssetPlat(plat)}.zip`;
|
|
152
|
-
|
|
156
|
+
log(`omnirush: downloading agent runtime (bun for ${plat})...`);
|
|
153
157
|
const buf = await get(zipUrl);
|
|
154
158
|
fs.mkdirSync(RUNTIME, { recursive: true });
|
|
155
159
|
extractZip(buf, ROOT);
|
|
@@ -157,7 +161,7 @@ async function fetchBun(plat) {
|
|
|
157
161
|
fs.copyFileSync(src, existing);
|
|
158
162
|
if (process.platform !== "win32") fs.chmodSync(existing, 0o755);
|
|
159
163
|
fs.rmSync(path.join(ROOT, `bun-${bunAssetPlat(plat)}`), { recursive: true, force: true });
|
|
160
|
-
|
|
164
|
+
log("omnirush: runtime ready");
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
// fd/rg release archives both nest the binary under a versioned directory
|
|
@@ -196,9 +200,14 @@ function rgAsset(plat) {
|
|
|
196
200
|
return `ripgrep-${RG_VERSION}-${arch}-unknown-linux-musl`;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Fetch the agent runtime when it is missing (idempotent: existing
|
|
205
|
+
* binaries are kept). Used by postinstall and by `omnirush doctor`
|
|
206
|
+
* self-heal. `log` defaults to console.log.
|
|
207
|
+
*/
|
|
208
|
+
export async function ensureRuntime({ log = console.log } = {}) {
|
|
200
209
|
const plat = platform();
|
|
201
|
-
await fetchBun(plat);
|
|
210
|
+
await fetchBun(plat, log);
|
|
202
211
|
for (const [name, version, repo, archive] of [
|
|
203
212
|
["fd", FD_VERSION, "sharkdp/fd", fdAsset(plat)],
|
|
204
213
|
["rg", RG_VERSION, "BurntSushi/ripgrep", rgAsset(plat)],
|
|
@@ -208,9 +217,17 @@ async function main() {
|
|
|
208
217
|
} catch (error) {
|
|
209
218
|
// Optional tools: warn and continue (the launcher only installs what
|
|
210
219
|
// exists; the agent degrades to PATH lookup / built-in search).
|
|
211
|
-
|
|
220
|
+
log(`omnirush: ${name} download failed (search may be degraded): ${error.message}`);
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
224
|
|
|
216
|
-
main()
|
|
225
|
+
async function main() {
|
|
226
|
+
await ensureRuntime();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// CLI entry (postinstall). Imported as a module by the launcher — never
|
|
230
|
+
// exit there.
|
|
231
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
232
|
+
main().catch((e) => { console.error("omnirush postinstall:", e.message); process.exit(1); });
|
|
233
|
+
}
|
package/src/bin.js
CHANGED
|
@@ -42,9 +42,11 @@ import {
|
|
|
42
42
|
modelsConfig,
|
|
43
43
|
resolveGatewayUrl,
|
|
44
44
|
withModelDefaults,
|
|
45
|
+
withDefaultThinkingLevel,
|
|
45
46
|
resolveModelDefaults,
|
|
46
47
|
} from "./lib.js";
|
|
47
48
|
import { installManagedTools } from "./tools.js";
|
|
49
|
+
import { ensureRuntime, managedBunPath } from "../scripts/postinstall.js";
|
|
48
50
|
import {
|
|
49
51
|
clearAuth,
|
|
50
52
|
deviceAuthorize,
|
|
@@ -70,6 +72,7 @@ const PI_AGENT_DIR = process.env.OMNIRUSH_PI_AGENT_DIR
|
|
|
70
72
|
? path.resolve(process.env.OMNIRUSH_PI_AGENT_DIR)
|
|
71
73
|
: path.join(os.homedir(), ".pi", "agent");
|
|
72
74
|
const PI_MODELS_JSON = path.join(PI_AGENT_DIR, "models.json");
|
|
75
|
+
const PI_SETTINGS_JSON = path.join(PI_AGENT_DIR, "settings.json");
|
|
73
76
|
const PI_EXT_DIR = path.join(PI_AGENT_DIR, "extensions", "omnirush");
|
|
74
77
|
|
|
75
78
|
function managerOrigin() {
|
|
@@ -79,7 +82,7 @@ function managerOrigin() {
|
|
|
79
82
|
);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
function
|
|
85
|
+
function piPackage() {
|
|
83
86
|
// The pi package is Bun-first and its "exports" map blocks plain Node
|
|
84
87
|
// resolution, so locate it by path: walk up from our own package root
|
|
85
88
|
// to the nearest node_modules containing it.
|
|
@@ -88,13 +91,22 @@ function piEntry() {
|
|
|
88
91
|
for (let i = 0; i < 6; i++) {
|
|
89
92
|
const pkgPath = path.join(dir, "node_modules", PKG, "package.json");
|
|
90
93
|
if (fs.existsSync(pkgPath)) {
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
try {
|
|
95
|
+
return { dir: path.dirname(pkgPath), pkg: JSON.parse(fs.readFileSync(pkgPath, "utf8")) };
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
const parent = path.dirname(dir);
|
|
95
101
|
if (parent === dir) break;
|
|
96
102
|
dir = parent;
|
|
97
103
|
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function piEntry() {
|
|
108
|
+
const found = piPackage();
|
|
109
|
+
if (found?.pkg?.bin?.pi) return path.join(found.dir, found.pkg.bin.pi);
|
|
98
110
|
throw new Error("could not locate the pinned pi CLI (@earendil-works/pi-coding-agent)");
|
|
99
111
|
}
|
|
100
112
|
|
|
@@ -107,6 +119,29 @@ function ensureDirs() {
|
|
|
107
119
|
} catch {
|
|
108
120
|
/* best effort — pi falls back to PATH, then its own downloader */
|
|
109
121
|
}
|
|
122
|
+
installDefaultThinkingLevel();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Product default effort (P2): xhigh. Fills an ABSENT
|
|
126
|
+
// defaultThinkingLevel in the agent settings only — an explicit user
|
|
127
|
+
// choice always wins; the models' thinkingLevelMap is untouched.
|
|
128
|
+
function installDefaultThinkingLevel() {
|
|
129
|
+
let settings = {};
|
|
130
|
+
try {
|
|
131
|
+
settings = JSON.parse(fs.readFileSync(PI_SETTINGS_JSON, "utf8"));
|
|
132
|
+
} catch {
|
|
133
|
+
/* fresh install or unparsable — start clean rather than clobber */
|
|
134
|
+
settings = {};
|
|
135
|
+
}
|
|
136
|
+
let next;
|
|
137
|
+
try {
|
|
138
|
+
next = withDefaultThinkingLevel(settings);
|
|
139
|
+
} catch {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (next !== settings) {
|
|
143
|
+
fs.writeFileSync(PI_SETTINGS_JSON, JSON.stringify(next, null, 2) + "\n");
|
|
144
|
+
}
|
|
110
145
|
}
|
|
111
146
|
|
|
112
147
|
// Merge our provider into the user's models.json without clobbering
|
|
@@ -154,36 +189,44 @@ function isMusl() {
|
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
function findBun() {
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
192
|
+
// MANAGED runtime first — always (issue #7: a system bun on PATH
|
|
193
|
+
// shadowed ours when .runtime was missing from a dev checkout, and a
|
|
194
|
+
// mismatched bun breaks the fork's own extensions). Vendored paths are
|
|
195
|
+
// canonical; PATH and ~/.bun are last-resort fallbacks that only run
|
|
196
|
+
// after a self-heal download was attempted. Linux packages ship both
|
|
197
|
+
// the glibc and the musl binary; the launcher picks by the running libc.
|
|
160
198
|
const runtimeRoot = path.resolve(__dirname, "..", ".runtime");
|
|
161
199
|
const plat = process.platform;
|
|
162
200
|
const arch = process.arch === "arm64" ? "aarch64" : "x64";
|
|
163
|
-
const
|
|
201
|
+
const managedCandidates = [];
|
|
164
202
|
if (plat === "win32") {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
);
|
|
168
|
-
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
169
|
-
if (dir) candidates.push(path.join(dir, "bun.exe"));
|
|
170
|
-
}
|
|
203
|
+
managedCandidates.push(managedBunPath());
|
|
204
|
+
managedCandidates.push(path.join(runtimeRoot, "bun-windows-x64", "bin", "bun.exe"));
|
|
171
205
|
} else {
|
|
172
206
|
if (plat === "linux") {
|
|
173
|
-
if (isMusl())
|
|
174
|
-
else if (arch === "x64")
|
|
175
|
-
|
|
207
|
+
if (isMusl()) managedCandidates.push(path.join(runtimeRoot, "bin", "bun-musl"));
|
|
208
|
+
else if (arch === "x64") managedCandidates.push(path.join(runtimeRoot, "bin", "bun"));
|
|
209
|
+
managedCandidates.push(path.join(runtimeRoot, "bin", `bun-${arch}${isMusl() ? "-musl" : ""}`));
|
|
176
210
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
211
|
+
managedCandidates.push(path.join(runtimeRoot, `bun-${plat}-${arch}`, "bin", "bun"));
|
|
212
|
+
}
|
|
213
|
+
for (const c of managedCandidates) {
|
|
214
|
+
try {
|
|
215
|
+
fs.accessSync(c, fs.constants.X_OK);
|
|
216
|
+
return { path: c, managed: true };
|
|
217
|
+
} catch {
|
|
218
|
+
/* next */
|
|
180
219
|
}
|
|
181
|
-
candidates.push(path.join(os.homedir(), ".bun", "bin", "bun"));
|
|
182
220
|
}
|
|
183
|
-
|
|
221
|
+
const fallbackCandidates = [];
|
|
222
|
+
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
223
|
+
if (dir) fallbackCandidates.push(path.join(dir, plat === "win32" ? "bun.exe" : "bun"));
|
|
224
|
+
}
|
|
225
|
+
fallbackCandidates.push(path.join(os.homedir(), ".bun", "bin", plat === "win32" ? "bun.exe" : "bun"));
|
|
226
|
+
for (const c of fallbackCandidates) {
|
|
184
227
|
try {
|
|
185
228
|
fs.accessSync(c, fs.constants.X_OK);
|
|
186
|
-
return c;
|
|
229
|
+
return { path: c, managed: false };
|
|
187
230
|
} catch {
|
|
188
231
|
/* next */
|
|
189
232
|
}
|
|
@@ -191,19 +234,57 @@ function findBun() {
|
|
|
191
234
|
return null;
|
|
192
235
|
}
|
|
193
236
|
|
|
194
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Self-healing managed-runtime resolution: if no MANAGED bun is
|
|
239
|
+
* installed, run the same fetch the postinstall runs, then re-check. A
|
|
240
|
+
* PATH bun is only used when the managed download is impossible — and
|
|
241
|
+
* the caller reports it.
|
|
242
|
+
*/
|
|
243
|
+
async function resolveBun() {
|
|
244
|
+
let found = findBun();
|
|
245
|
+
if (found?.managed) return found;
|
|
246
|
+
// Self-heal: fetch the managed runtime (downloads only what is
|
|
247
|
+
// missing; a few MB-100MB depending on platform).
|
|
248
|
+
try {
|
|
249
|
+
await ensureRuntime();
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// fall through: maybe a usable fallback still exists
|
|
252
|
+
try {
|
|
253
|
+
fs.accessSync(managedBunPath(), fs.constants.X_OK);
|
|
254
|
+
found = { path: managedBunPath(), managed: true };
|
|
255
|
+
} catch {
|
|
256
|
+
console.error(`omnirush: managed runtime download failed (${error?.message ?? error})`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!found?.managed) found = findBun();
|
|
260
|
+
return found;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runtimeFor(entry) {
|
|
195
264
|
// pi is Bun-first; its CLI imports node:module#enableCompileCache,
|
|
196
|
-
// which needs Node >= 22.8.
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
265
|
+
// which needs Node >= 22.8. The MANAGED bun wins unconditionally
|
|
266
|
+
// (self-heal download first); a PATH bun is a last resort. Plain node
|
|
267
|
+
// 22.8+ runs the agent without any bun.
|
|
268
|
+
const found = await resolveBun();
|
|
269
|
+
if (found) {
|
|
270
|
+
if (!found.managed) {
|
|
271
|
+
console.error(
|
|
272
|
+
"omnirush: using a non-managed bun from PATH " +
|
|
273
|
+
`(${found.path}) — the managed runtime is missing. Fix: run ` +
|
|
274
|
+
"`npm install -g omnirush` again (retries the runtime download).",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return { cmd: found.path, args: [entry] };
|
|
278
|
+
}
|
|
200
279
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
201
280
|
if (major > 22 || (major === 22 && minor >= 8)) {
|
|
202
281
|
return { cmd: process.execPath, args: [entry] };
|
|
203
282
|
}
|
|
204
283
|
console.error(
|
|
205
|
-
`omnirush
|
|
206
|
-
"
|
|
284
|
+
`omnirush couldn't set up its agent runtime (Node ${process.versions.node} is too ` +
|
|
285
|
+
"old for the agent core, which needs Node >= 22.8, and the managed runtime " +
|
|
286
|
+
"download did not succeed — network or install issue). Fix: run " +
|
|
287
|
+
"`npm install -g omnirush` again (retries the runtime download), or install Node 22.8+.",
|
|
207
288
|
);
|
|
208
289
|
process.exit(1);
|
|
209
290
|
}
|
|
@@ -421,6 +502,72 @@ async function cmdDoctor() {
|
|
|
421
502
|
}
|
|
422
503
|
ok("agent config", true, PI_MODELS_JSON);
|
|
423
504
|
ok("extensions", true, PI_EXT_DIR);
|
|
505
|
+
// Agent core version: a stale core predates the omnirush provider and
|
|
506
|
+
// branding and shows pi.dev "Update Available" banners (Foxtran's
|
|
507
|
+
// 0.73.1 report). Advisory WARN with the fix; exit code unaffected.
|
|
508
|
+
{
|
|
509
|
+
const found = piPackage();
|
|
510
|
+
const coreVersion = String(found?.pkg?.version ?? "");
|
|
511
|
+
const own = ownVersion();
|
|
512
|
+
if (!coreVersion) {
|
|
513
|
+
console.log(`WARN agent core — pinned agent package not found (broken install). Fix: omnirush update`);
|
|
514
|
+
} else if (coreVersion === own) {
|
|
515
|
+
ok("agent core", true, `v${coreVersion}`);
|
|
516
|
+
} else {
|
|
517
|
+
console.log(
|
|
518
|
+
`WARN agent core — installed core is v${coreVersion}, this omnirush expects v${own} ` +
|
|
519
|
+
"(stale install: pi.dev update banners, missing omnirush provider). Fix: omnirush update",
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Agent runtime: the managed bun must exist and always wins (issue #7:
|
|
524
|
+
// a PATH bun shadowed the managed runtime for a dev). Self-heal by
|
|
525
|
+
// downloading it right here when missing; report WHICH runtime is in
|
|
526
|
+
// use so support can see it.
|
|
527
|
+
{
|
|
528
|
+
let found = findBun();
|
|
529
|
+
let healError = null;
|
|
530
|
+
if (!found?.managed) {
|
|
531
|
+
try {
|
|
532
|
+
await ensureRuntime();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
healError = error;
|
|
535
|
+
}
|
|
536
|
+
found = findBun();
|
|
537
|
+
}
|
|
538
|
+
if (found?.managed) {
|
|
539
|
+
ok(
|
|
540
|
+
"runtime",
|
|
541
|
+
true,
|
|
542
|
+
`bundled bun ${bunVersionOf(found.path)} (${found.path})`,
|
|
543
|
+
);
|
|
544
|
+
} else if (healError) {
|
|
545
|
+
ok(
|
|
546
|
+
"runtime",
|
|
547
|
+
false,
|
|
548
|
+
`managed runtime missing and the download failed (${healError?.message ?? healError}) — ` +
|
|
549
|
+
"Fix: run `npm install -g omnirush` again (retries the runtime download), or install Node 22.8+",
|
|
550
|
+
);
|
|
551
|
+
} else if (found) {
|
|
552
|
+
console.log(
|
|
553
|
+
`WARN runtime — using a NON-managed bun ${bunVersionOf(found.path)} from PATH (${found.path}); ` +
|
|
554
|
+
"the managed runtime is missing. Fix: run `npm install -g omnirush` again (retries the runtime download)",
|
|
555
|
+
);
|
|
556
|
+
} else {
|
|
557
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
558
|
+
if (major > 22 || (major === 22 && minor >= 8)) {
|
|
559
|
+
ok("runtime", true, "no bun needed — Node " + process.versions.node + " runs the agent");
|
|
560
|
+
} else {
|
|
561
|
+
ok(
|
|
562
|
+
"runtime",
|
|
563
|
+
false,
|
|
564
|
+
`Node ${process.versions.node} is too old for the agent core (need >= 22.8) and the managed ` +
|
|
565
|
+
"runtime is missing — Fix: run `npm install -g omnirush` again (retries the runtime download), " +
|
|
566
|
+
"or install Node 22.8+",
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
424
571
|
// Search tools: bundled fd/rg (or system ones) keep the agent off its
|
|
425
572
|
// GitHub download path, which fails on restricted networks.
|
|
426
573
|
const toolsRoot = path.join(PI_AGENT_DIR, "bin");
|
|
@@ -465,6 +612,23 @@ function requireAuth() {
|
|
|
465
612
|
process.exit(1);
|
|
466
613
|
}
|
|
467
614
|
|
|
615
|
+
function bunVersionOf(bunPath) {
|
|
616
|
+
try {
|
|
617
|
+
const { execFileSync } = require("node:child_process");
|
|
618
|
+
return String(execFileSync(bunPath, ["--version"], { timeout: 5000 })).trim() || "unknown";
|
|
619
|
+
} catch {
|
|
620
|
+
return "unknown";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function ownVersion() {
|
|
625
|
+
try {
|
|
626
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
|
|
627
|
+
} catch {
|
|
628
|
+
return "unknown";
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
468
632
|
function childEnv(auth) {
|
|
469
633
|
const gateway = agentGatewayUrl(auth);
|
|
470
634
|
const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
|
|
@@ -475,34 +639,85 @@ function childEnv(auth) {
|
|
|
475
639
|
OMNIRUSH_ORIGIN: origin,
|
|
476
640
|
OMNIRUSH_GATEWAY_URL: gateway,
|
|
477
641
|
OMNIRUSH_DIR: OMNI_DIR,
|
|
642
|
+
// /status shows the CLI version; extensions have no package.json of
|
|
643
|
+
// their own in ~/.pi, so the launcher hands it over.
|
|
644
|
+
OMNIRUSH_VERSION: ownVersion(),
|
|
478
645
|
// The agent resolves its config dir from <APP>_CODING_AGENT_DIR. The
|
|
479
646
|
// branding patch renames the app to omnirush, so set the omnirush
|
|
480
647
|
// spelling; the pi spelling stays for unpatched cores.
|
|
481
648
|
OMNIRUSH_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
482
649
|
PI_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
483
650
|
// The agent core is a fork/rebrand: never phone home to pi.dev.
|
|
651
|
+
// PI_OFFLINE kills startup update checks (the "Update Available —
|
|
652
|
+
// pi.dev/changelog" banner), package updates, and install/update
|
|
653
|
+
// telemetry; PI_TELEMETRY stays for cores that predate PI_OFFLINE.
|
|
654
|
+
PI_OFFLINE: "1",
|
|
484
655
|
PI_TELEMETRY: "0",
|
|
485
656
|
};
|
|
486
657
|
}
|
|
487
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Spawn the agent with launcher signal hygiene (issue #9). The terminal's
|
|
661
|
+
* Ctrl+C already reaches the whole foreground process group — the child
|
|
662
|
+
* (bun -> pi) handles it itself (first ^C interrupts the turn, second
|
|
663
|
+
* exits pi). The launcher therefore IGNORES SIGINT (it used to die
|
|
664
|
+
* immediately, orphaning the TUI and forcing a third ^C), forwards
|
|
665
|
+
* process-directed signals, and exits only when the child does, with the
|
|
666
|
+
* child's exit code or signal.
|
|
667
|
+
*/
|
|
668
|
+
function spawnAgent(cmd, args, opts) {
|
|
669
|
+
const child = spawn(cmd, args, opts);
|
|
670
|
+
let childAlive = true;
|
|
671
|
+
let lastExit = { code: 0, signal: null };
|
|
672
|
+
child.on("exit", (code, signal) => {
|
|
673
|
+
childAlive = false;
|
|
674
|
+
lastExit = { code, signal };
|
|
675
|
+
});
|
|
676
|
+
const onInterrupt = () => {
|
|
677
|
+
if (!childAlive) {
|
|
678
|
+
process.exit(lastExit.code ?? 0);
|
|
679
|
+
}
|
|
680
|
+
// Otherwise let pi handle the interrupt — never double-send, never
|
|
681
|
+
// exit first.
|
|
682
|
+
};
|
|
683
|
+
const forward = (signal) => () => {
|
|
684
|
+
if (childAlive) {
|
|
685
|
+
try {
|
|
686
|
+
child.kill(signal);
|
|
687
|
+
} catch {
|
|
688
|
+
/* already gone */
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
process.exit(lastExit.code ?? 0);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
process.on("SIGINT", onInterrupt);
|
|
695
|
+
process.on("SIGTERM", forward("SIGTERM"));
|
|
696
|
+
if (process.platform !== "win32") {
|
|
697
|
+
process.on("SIGHUP", forward("SIGHUP"));
|
|
698
|
+
process.on("SIGQUIT", forward("SIGQUIT"));
|
|
699
|
+
}
|
|
700
|
+
child.on("exit", (code, signal) => {
|
|
701
|
+
if (signal) process.kill(process.pid, signal);
|
|
702
|
+
else process.exit(code ?? 0);
|
|
703
|
+
});
|
|
704
|
+
return child;
|
|
705
|
+
}
|
|
706
|
+
|
|
488
707
|
async function cmdRun() {
|
|
489
708
|
ensureDirs();
|
|
490
709
|
const auth = requireAuth();
|
|
491
710
|
installModelsJson(agentGatewayUrl(auth));
|
|
492
711
|
installExtensions();
|
|
493
712
|
const entry = piEntry();
|
|
494
|
-
const { cmd, args } = runtimeFor(entry);
|
|
713
|
+
const { cmd, args } = await runtimeFor(entry);
|
|
495
714
|
// Plain `omnirush` always runs the SOTA model (charter rule 1); user
|
|
496
715
|
// flags win when present.
|
|
497
716
|
const piArgs = withModelDefaults([], resolveModelDefaults(process.env));
|
|
498
|
-
|
|
717
|
+
spawnAgent(cmd, [...args, ...piArgs], {
|
|
499
718
|
stdio: "inherit",
|
|
500
719
|
env: { ...process.env, ...childEnv(auth) },
|
|
501
720
|
});
|
|
502
|
-
child.on("exit", (code, signal) => {
|
|
503
|
-
if (signal) process.kill(process.pid, signal);
|
|
504
|
-
else process.exit(code ?? 0);
|
|
505
|
-
});
|
|
506
721
|
}
|
|
507
722
|
|
|
508
723
|
async function cmdCollect(argv) {
|
|
@@ -510,24 +725,63 @@ async function cmdCollect(argv) {
|
|
|
510
725
|
const auth = requireAuth();
|
|
511
726
|
installExtensions();
|
|
512
727
|
const entry = path.join(__dirname, "..", "assets", "collect-once.ts");
|
|
513
|
-
const { cmd, args } = runtimeFor(entry);
|
|
514
|
-
|
|
728
|
+
const { cmd, args } = await runtimeFor(entry);
|
|
729
|
+
spawnAgent(cmd, [...args, ...argv], {
|
|
515
730
|
stdio: "inherit",
|
|
516
731
|
env: { ...process.env, ...childEnv(auth) },
|
|
517
732
|
});
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Native omnirush help (issue #8): --help/-h must render OUR commands
|
|
736
|
+
// and branding — never forward to the agent core's help.
|
|
737
|
+
function printHelp() {
|
|
738
|
+
console.log(`omnirush — free daily tokens for the most powerful coding model on earth
|
|
739
|
+
|
|
740
|
+
Usage:
|
|
741
|
+
omnirush start the agent (default: gpt-6-astra)
|
|
742
|
+
omnirush -p "<prompt>" run a one-shot prompt
|
|
743
|
+
omnirush login sign in (device flow via your browser)
|
|
744
|
+
omnirush logout forget the stored device tokens
|
|
745
|
+
omnirush whoami show the signed-in identity
|
|
746
|
+
omnirush doctor check auth, manager, runtime, agent config
|
|
747
|
+
omnirush collect [--dir PATH] one-shot workspace snapshot upload
|
|
748
|
+
omnirush update update omnirush (npm install -g omnirush@latest)
|
|
749
|
+
omnirush --version show the omnirush version
|
|
750
|
+
|
|
751
|
+
Options:
|
|
752
|
+
--model <model[:effort]> model override (gpt-6-astra, gpt-5.6-sol;
|
|
753
|
+
effort: minimal..max)
|
|
754
|
+
--thinking <level> reasoning effort override
|
|
755
|
+
--strict-sota exit non-zero if the served model is not
|
|
756
|
+
the requested SOTA model
|
|
757
|
+
--session <file> | --continue session options (forwarded to the agent)
|
|
758
|
+
-p, --print "<prompt>" print-mode: answer once and exit
|
|
759
|
+
-h, --help show this help
|
|
760
|
+
|
|
761
|
+
Session commands (inside the agent):
|
|
762
|
+
/grant today's token grant and usage
|
|
763
|
+
/usage detailed usage from the manager (counters + devices)
|
|
764
|
+
/status session status (identity, model, grant, collector)
|
|
765
|
+
/mcp MCP server state (/mcp reconnect [server])
|
|
766
|
+
/verify model attestation (arrives with the gateway lane)
|
|
767
|
+
|
|
768
|
+
Environment:
|
|
769
|
+
OMNIRUSH_ORIGIN manager origin override
|
|
770
|
+
OMNIRUSH_MODEL default model override (e.g. gpt-5.6-sol)
|
|
771
|
+
OMNIRUSH_DIR state dir override (default ~/.omnirush)
|
|
772
|
+
OMNIRUSH_DEBUG=1 verbose diagnostics (raw gateway errors on failure)
|
|
773
|
+
|
|
774
|
+
Docs: https://omnirush.ai/omnirush — state is stored 0600 in ~/.omnirush`);
|
|
522
775
|
}
|
|
523
776
|
|
|
524
777
|
const [, , ...argv] = process.argv;
|
|
525
778
|
const cmd = argv[0];
|
|
526
|
-
if (cmd === "
|
|
779
|
+
if (cmd === "-h" || cmd === "--help" || cmd === "help") printHelp();
|
|
780
|
+
else if (cmd === "login") await cmdLogin(argv.slice(1));
|
|
527
781
|
else if (cmd === "logout") await cmdLogout();
|
|
528
782
|
else if (cmd === "whoami") await cmdWhoami();
|
|
529
783
|
else if (cmd === "doctor") await cmdDoctor();
|
|
530
|
-
else if (cmd === "--version" || cmd === "-v") console.log(
|
|
784
|
+
else if (cmd === "--version" || cmd === "-v") console.log(`omnirush ${ownVersion()}`);
|
|
531
785
|
else if (cmd === "collect") await cmdCollect(argv.slice(1));
|
|
532
786
|
else if (cmd === undefined || cmd === "run") await cmdRun();
|
|
533
787
|
else {
|
|
@@ -540,11 +794,10 @@ else {
|
|
|
540
794
|
installModelsJson(agentGatewayUrl(auth));
|
|
541
795
|
installExtensions();
|
|
542
796
|
const entry = piEntry();
|
|
543
|
-
const { cmd: bin, args } = runtimeFor(entry);
|
|
797
|
+
const { cmd: bin, args } = await runtimeFor(entry);
|
|
544
798
|
const piArgs = withModelDefaults(argv, resolveModelDefaults(process.env));
|
|
545
|
-
|
|
799
|
+
spawnAgent(bin, [...args, ...piArgs], {
|
|
546
800
|
stdio: "inherit",
|
|
547
801
|
env: { ...process.env, ...childEnv(auth) },
|
|
548
802
|
});
|
|
549
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
550
803
|
}
|
package/src/lib.js
CHANGED
|
@@ -221,6 +221,19 @@ export function modelsConfig({ gatewayUrl } = {}) {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Product default effort (Dan's call, 2026-09-24): xhigh, not pi's
|
|
226
|
+
* medium. The `thinkingLevelMap` on each model is unchanged — this only
|
|
227
|
+
* fills an ABSENT `defaultThinkingLevel` in the agent's settings.json,
|
|
228
|
+
* so an explicit user choice (saved via Ctrl+S in /thinking, or edited
|
|
229
|
+
* manually) always wins. Returns the settings object to persist; pure.
|
|
230
|
+
*/
|
|
231
|
+
export function withDefaultThinkingLevel(settings, level = "xhigh") {
|
|
232
|
+
const base = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
233
|
+
if (base.defaultThinkingLevel !== undefined) return base;
|
|
234
|
+
return { ...base, defaultThinkingLevel: level };
|
|
235
|
+
}
|
|
236
|
+
|
|
224
237
|
/**
|
|
225
238
|
* Pure mapping of the bundled search-tool binaries for the launcher: pi
|
|
226
239
|
* looks for managed `fd`/`rg` in <agentDir>/bin first and downloads from
|