omnirush 0.8.5 → 0.8.6
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 +6 -5
- package/assets/extensions/omnirush/agents-lib.ts +66 -3
- package/assets/extensions/omnirush/agents.ts +62 -14
- package/assets/extensions/omnirush/auth.js +399 -115
- package/assets/extensions/omnirush/collector.ts +31 -5
- package/assets/extensions/omnirush/commands.ts +12 -15
- package/assets/extensions/omnirush/sota.ts +52 -43
- package/package.json +2 -2
- package/src/bin.js +48 -21
|
@@ -255,7 +255,11 @@ export default function (pi: any) {
|
|
|
255
255
|
if ((process.env.OMNIRUSH_PARENT_SESSION || "").trim()) return;
|
|
256
256
|
const origin = resolveOrigin(process.env);
|
|
257
257
|
const gatewayUrl = process.env.OMNIRUSH_GATEWAY_URL || gatewayUrlForOrigin(origin);
|
|
258
|
-
|
|
258
|
+
// The shared auth file is the source of truth (another process may have
|
|
259
|
+
// rotated since launch); the launcher's environment value is a fallback.
|
|
260
|
+
const accessToken = (
|
|
261
|
+
sharedRefresher().accessToken() || process.env.OMNIRUSH_ACCESS_TOKEN || process.env.OMNIRUSH_TOKEN || ""
|
|
262
|
+
).trim();
|
|
259
263
|
const stateDir = omniDir();
|
|
260
264
|
const agentDir = piAgentDir();
|
|
261
265
|
const version = cliVersion();
|
|
@@ -275,19 +279,40 @@ export default function (pi: any) {
|
|
|
275
279
|
// sota guard shares it): a 401 asks it for the token after `current`.
|
|
276
280
|
let current = accessToken;
|
|
277
281
|
const refreshAccessToken = async (): Promise<string | null> => {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
const next =
|
|
281
|
-
if (!refreshed || !next) return null;
|
|
282
|
+
const outcome = await sharedRefresher().recover(current).catch(() => null);
|
|
283
|
+
if (outcome?.status !== "ok") return null;
|
|
284
|
+
const next = outcome.accessToken;
|
|
282
285
|
current = next;
|
|
283
286
|
archiver.setAccessToken(next);
|
|
284
287
|
return next;
|
|
285
288
|
};
|
|
286
289
|
|
|
290
|
+
// Uploads carry the CURRENT on-disk token, not the one this process
|
|
291
|
+
// last saw: another terminal or a sub-agent may have rotated the pair.
|
|
292
|
+
// Only requests that already carry a bearer (collect, archive API) are
|
|
293
|
+
// touched — presigned S3 part PUTs pass through unchanged.
|
|
294
|
+
const withCurrentBearer = (input: string, init?: RequestInit): Promise<Response> => {
|
|
295
|
+
const headers = new Headers(init?.headers ?? {});
|
|
296
|
+
if (/^Bearer /i.test(headers.get("authorization") ?? "")) {
|
|
297
|
+
let latest = "";
|
|
298
|
+
try {
|
|
299
|
+
latest = sharedRefresher().accessToken();
|
|
300
|
+
} catch {
|
|
301
|
+
/* keep the caller's bearer */
|
|
302
|
+
}
|
|
303
|
+
if (latest) {
|
|
304
|
+
headers.set("Authorization", `Bearer ${latest}`);
|
|
305
|
+
return globalThis.fetch(input, { ...init, headers });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return globalThis.fetch(input, init);
|
|
309
|
+
};
|
|
310
|
+
|
|
287
311
|
const collector = new WorkspaceCollector({
|
|
288
312
|
gatewayUrl,
|
|
289
313
|
accessToken,
|
|
290
314
|
refreshAccessToken,
|
|
315
|
+
fetch: withCurrentBearer,
|
|
291
316
|
stateDir,
|
|
292
317
|
log,
|
|
293
318
|
// environment.app_version tells the CLI apart from the desktop app (whose
|
|
@@ -309,6 +334,7 @@ export default function (pi: any) {
|
|
|
309
334
|
gatewayUrl,
|
|
310
335
|
accessToken,
|
|
311
336
|
refreshAccessToken,
|
|
337
|
+
fetch: withCurrentBearer,
|
|
312
338
|
// The CLI's own state (auth, spool, archive queue) and the agent's
|
|
313
339
|
// session files are never archived when they sit inside a project.
|
|
314
340
|
excludedDirs: [stateDir, agentDir],
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { readSessionLedger, SESSION_LEDGER_FILE } from "./capture/workspace-collector";
|
|
21
|
-
import { deviceMe, omniDir, resolveOrigin } from "./auth";
|
|
21
|
+
import { deviceMe, omniDir, resolveOrigin, sessionEndedMessage } from "./auth";
|
|
22
22
|
import { sharedRefresher } from "./refresh";
|
|
23
23
|
import { formatCollectorLine, formatStatusLines } from "./status-lib";
|
|
24
24
|
import {
|
|
@@ -42,27 +42,24 @@ interface MeResult {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Fetch the signed-in identity + server-side usage with the
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* Fetch the signed-in identity + server-side usage with the current
|
|
46
|
+
* on-disk token; a 401 adopts a newer on-disk pair or refreshes under the
|
|
47
|
+
* cross-process lock and retries (auth.js). Never throws; errors come back
|
|
48
|
+
* as `error` strings safe to display.
|
|
49
49
|
*/
|
|
50
50
|
async function fetchMe(): Promise<MeResult> {
|
|
51
51
|
const origin = resolveOrigin(process.env);
|
|
52
52
|
const refresher = sharedRefresher();
|
|
53
|
-
|
|
54
|
-
// in-flight 401 recovery — and fall back to the launcher-provided one.
|
|
55
|
-
const token = refresher.auth?.accessToken || (process.env.OMNIRUSH_TOKEN || "").trim();
|
|
53
|
+
let token = refresher.accessToken() || (process.env.OMNIRUSH_TOKEN || "").trim();
|
|
56
54
|
if (!token) return { me: null, error: "not signed in — run `omnirush login` first" };
|
|
57
55
|
try {
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
// 401: single-flight refresh, retry once with the rotated token.
|
|
61
|
-
const refreshed = await refresher.refresh(token).catch(() => false);
|
|
62
|
-
const next = refresher.auth?.accessToken;
|
|
63
|
-
if (refreshed && next && next !== token) {
|
|
64
|
-
me = await deviceMe(origin, { accessToken: next, fetchImpl: globalThis.fetch });
|
|
56
|
+
for (let round = 0; round < 3; round++) {
|
|
57
|
+
const me = await deviceMe(origin, { accessToken: token, fetchImpl: globalThis.fetch });
|
|
65
58
|
if (me !== null) return { me };
|
|
59
|
+
const outcome = await refresher.recover(token);
|
|
60
|
+
if (outcome.status === "transient") return { me: null, error: `manager unreachable (${outcome.error})` };
|
|
61
|
+
if (outcome.status !== "ok") return { me: null, error: sessionEndedMessage(outcome).replace(/^Omnirush: /, "") };
|
|
62
|
+
token = outcome.accessToken;
|
|
66
63
|
}
|
|
67
64
|
return { me: null, error: "credentials rejected — run `omnirush login` again" };
|
|
68
65
|
} catch (error) {
|
|
@@ -31,6 +31,7 @@ import fs from "node:fs";
|
|
|
31
31
|
import { openAIResponsesApi } from "@earendil-works/pi-ai";
|
|
32
32
|
import { recordGatewayUsage } from "./usage";
|
|
33
33
|
import { sharedRefresher } from "./refresh";
|
|
34
|
+
import { sendWithDeviceAuth, sessionEndedMessage } from "./auth";
|
|
34
35
|
import {
|
|
35
36
|
createSseDataScanner,
|
|
36
37
|
formatSotaWarning,
|
|
@@ -202,8 +203,9 @@ async function readErrorBody(response: any): Promise<string> {
|
|
|
202
203
|
|
|
203
204
|
/**
|
|
204
205
|
* Wrap a fetch implementation so that:
|
|
205
|
-
* -
|
|
206
|
-
*
|
|
206
|
+
* - every attempt carries the current on-disk device token, and a 401
|
|
207
|
+
* adopts a newer on-disk pair or refreshes under the cross-process
|
|
208
|
+
* lock, then resends (auth.js sendWithDeviceAuth),
|
|
207
209
|
* - 429 / 5xx / network failures retry with exponential backoff + full
|
|
208
210
|
* jitter (progress lines on stderr), and
|
|
209
211
|
* - SSE response bodies stream through a pass-through tap that observes
|
|
@@ -231,47 +233,46 @@ function tapFetch(
|
|
|
231
233
|
|
|
232
234
|
const outcome = await withRetries(
|
|
233
235
|
async (attempt, ref) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/* usage capture must never break the request */
|
|
244
|
-
}
|
|
245
|
-
if (response?.status === 401) {
|
|
246
|
-
const tokenUsed = bearerFromInit(init) || bearerFromInit(input);
|
|
247
|
-
debug(`401 seen; bearer present: ${Boolean(tokenUsed)}`);
|
|
248
|
-
if (tokenUsed) {
|
|
249
|
-
const refresher = sharedRefresher();
|
|
250
|
-
const refreshed = await refresher.refresh(tokenUsed).catch((error: any) => {
|
|
251
|
-
debug(`refresh threw: ${error?.message ?? error}`);
|
|
252
|
-
return false;
|
|
253
|
-
});
|
|
254
|
-
const next = refresher.auth?.accessToken;
|
|
255
|
-
debug(`refresh ok: ${refreshed}, next token present: ${Boolean(next)}`);
|
|
256
|
-
if (refreshed && next) {
|
|
257
|
-
response = await doFetch(next);
|
|
258
|
-
debug(`retry-after-refresh status: ${response?.status}`);
|
|
259
|
-
try {
|
|
260
|
-
recordGatewayUsage(response?.headers);
|
|
261
|
-
} catch {
|
|
262
|
-
/* as above */
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (response?.status === 401) {
|
|
266
|
-
// The refresh (or the fresh-adopted token) was rejected:
|
|
267
|
-
// the session is dead. Retrying forever would spin pi's
|
|
268
|
-
// auto-retry loop silently — fail fast with the fix.
|
|
269
|
-
warnStderr(
|
|
270
|
-
"Omnirush: this device's session is no longer valid — run `omnirush login` to sign in again.",
|
|
271
|
-
);
|
|
272
|
-
process.exit(2);
|
|
273
|
-
}
|
|
236
|
+
// Every attempt carries the CURRENT on-disk token (re-read per
|
|
237
|
+
// request: another terminal or a sub-agent may have rotated the
|
|
238
|
+
// pair since this process started). A 401 adopts a newer on-disk
|
|
239
|
+
// pair or refreshes under the cross-process lock, then resends.
|
|
240
|
+
const recordUsage = (res: any) => {
|
|
241
|
+
try {
|
|
242
|
+
recordGatewayUsage(res?.headers);
|
|
243
|
+
} catch {
|
|
244
|
+
/* usage capture must never break the request */
|
|
274
245
|
}
|
|
246
|
+
};
|
|
247
|
+
const { response: answered, outcome: authOutcome } = await sendWithDeviceAuth(
|
|
248
|
+
(token: string) => doFetch(token || undefined),
|
|
249
|
+
sharedRefresher(),
|
|
250
|
+
{
|
|
251
|
+
fallbackToken: bearerFromInit(init) || bearerFromInit(input),
|
|
252
|
+
onResponse: (res: any) => {
|
|
253
|
+
debug(`provider fetch ${requestUrl} -> ${res?.status} (attempt ${attempt})`);
|
|
254
|
+
recordUsage(res);
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
let response = answered;
|
|
259
|
+
if (authOutcome?.status === "dead") {
|
|
260
|
+
// The manager refused the refresh token that is on disk right
|
|
261
|
+
// now (re-read under the refresh lock): the device session is
|
|
262
|
+
// gone (revoked, expired, signed out). Retrying would spin pi's
|
|
263
|
+
// auto-retry loop silently — fail fast with the fix.
|
|
264
|
+
debug(`device session ended: ${authOutcome.reason} ${authOutcome.detail ?? ""}`);
|
|
265
|
+
warnStderr(sessionEndedMessage(authOutcome));
|
|
266
|
+
process.exit(2);
|
|
267
|
+
}
|
|
268
|
+
if (authOutcome?.status === "transient") {
|
|
269
|
+
// Could not reach the manager to refresh: not a sign-out. Retry
|
|
270
|
+
// like any other brief outage.
|
|
271
|
+
debug(`refresh unavailable: ${authOutcome.error}`);
|
|
272
|
+
response = new Response(
|
|
273
|
+
JSON.stringify({ error: { message: "Omnirush sign-in service unavailable", type: "omnirush_error", code: "auth_refresh_unavailable" } }),
|
|
274
|
+
{ status: 503, headers: { "content-type": "application/json" } },
|
|
275
|
+
);
|
|
275
276
|
}
|
|
276
277
|
const retryAfterSec = Number(response?.headers?.get?.("retry-after")) || undefined;
|
|
277
278
|
return { response, ref, retryAfterSec };
|
|
@@ -415,7 +416,15 @@ export default function (pi: any) {
|
|
|
415
416
|
apiKey: {
|
|
416
417
|
name: "Omnirush token",
|
|
417
418
|
async resolve() {
|
|
418
|
-
|
|
419
|
+
// The shared auth file first (always current); the launcher's
|
|
420
|
+
// environment value only as a bootstrap.
|
|
421
|
+
let key = "";
|
|
422
|
+
try {
|
|
423
|
+
key = sharedRefresher().accessToken();
|
|
424
|
+
} catch {
|
|
425
|
+
/* fall back to the environment */
|
|
426
|
+
}
|
|
427
|
+
key ||= (process.env.OMNIRUSH_TOKEN || "").trim();
|
|
419
428
|
if (!key) return undefined;
|
|
420
429
|
return { auth: { apiKey: key }, source: "environment" };
|
|
421
430
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnirush",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Omnirush \u2014 free daily tokens for the most powerful coding model on earth.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -41,4 +41,4 @@
|
|
|
41
41
|
"minimatch": "^10.2.6",
|
|
42
42
|
"zod": "^4.6.5"
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
}
|
package/src/bin.js
CHANGED
|
@@ -60,10 +60,11 @@ import {
|
|
|
60
60
|
openBrowser,
|
|
61
61
|
pollForToken,
|
|
62
62
|
redactForDisplay,
|
|
63
|
-
refreshTokens,
|
|
64
63
|
saveAuth,
|
|
65
64
|
savePendingFlow,
|
|
66
65
|
clearPendingFlow,
|
|
66
|
+
createRefresher,
|
|
67
|
+
sessionEndedMessage,
|
|
67
68
|
} from "../assets/extensions/omnirush/auth.js";
|
|
68
69
|
|
|
69
70
|
const require = createRequire(import.meta.url);
|
|
@@ -162,10 +163,21 @@ async function launchModels(gatewayUrl, accessToken) {
|
|
|
162
163
|
const base = String(gatewayUrl).replace(/\/+$/, "");
|
|
163
164
|
if (process.env.OMNIRUSH_STATIC_MODELS !== "1" && accessToken) {
|
|
164
165
|
try {
|
|
165
|
-
const
|
|
166
|
-
headers: { Authorization: `Bearer ${
|
|
166
|
+
const get = (token) => fetch(`${base}/models`, {
|
|
167
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
167
168
|
signal: AbortSignal.timeout(2500),
|
|
168
169
|
});
|
|
170
|
+
let response = await get(accessToken);
|
|
171
|
+
if (response.status === 401) {
|
|
172
|
+
// The stored access token expired (or another process rotated):
|
|
173
|
+
// refresh now, under the shared lock, so the agent starts with a
|
|
174
|
+
// working pair on disk.
|
|
175
|
+
await response.body?.cancel().catch(() => undefined);
|
|
176
|
+
const outcome = await createRefresher({ dir: OMNI_DIR, origin: managerOrigin() })
|
|
177
|
+
.recover(accessToken)
|
|
178
|
+
.catch(() => null);
|
|
179
|
+
if (outcome?.status === "ok") response = await get(outcome.accessToken);
|
|
180
|
+
}
|
|
169
181
|
if (response.ok) {
|
|
170
182
|
const payload = await response.json();
|
|
171
183
|
const models = catalogFromGateway(payload);
|
|
@@ -444,15 +456,24 @@ async function cmdLogout() {
|
|
|
444
456
|
console.log(`Signed out. Credentials removed from ${OMNI_DIR}`);
|
|
445
457
|
}
|
|
446
458
|
|
|
447
|
-
|
|
459
|
+
/**
|
|
460
|
+
* GET /device/me with the current on-disk token; a 401 adopts a pair
|
|
461
|
+
* another process already rotated, or refreshes under the shared refresh
|
|
462
|
+
* lock (never a bare refresh that could race a running agent). Resolves
|
|
463
|
+
* {me} or {me: null, outcome} with the recover() verdict.
|
|
464
|
+
*/
|
|
465
|
+
async function fetchMeWithRefresh() {
|
|
448
466
|
const origin = managerOrigin();
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
467
|
+
const refresher = createRefresher({ dir: OMNI_DIR, origin });
|
|
468
|
+
let token = refresher.accessToken();
|
|
469
|
+
for (let round = 0; round < 3; round++) {
|
|
470
|
+
const me = await deviceMe(origin, { accessToken: token, fetchImpl: globalThis.fetch });
|
|
471
|
+
if (me !== null) return { me };
|
|
472
|
+
const outcome = await refresher.recover(token);
|
|
473
|
+
if (outcome.status !== "ok") return { me: null, outcome };
|
|
474
|
+
token = outcome.accessToken;
|
|
475
|
+
}
|
|
476
|
+
return { me: null, outcome: { status: "dead", reason: "session", detail: "access token refused after refresh" } };
|
|
456
477
|
}
|
|
457
478
|
|
|
458
479
|
async function cmdWhoami() {
|
|
@@ -461,9 +482,13 @@ async function cmdWhoami() {
|
|
|
461
482
|
console.error("Omnirush: not signed in. Run `omnirush login` first.");
|
|
462
483
|
process.exit(1);
|
|
463
484
|
}
|
|
464
|
-
const { me } = await fetchMeWithRefresh(
|
|
485
|
+
const { me, outcome } = await fetchMeWithRefresh();
|
|
465
486
|
if (!me) {
|
|
466
|
-
console.error(
|
|
487
|
+
console.error(
|
|
488
|
+
outcome?.status === "transient"
|
|
489
|
+
? `Omnirush: cannot reach the manager to verify the session (${outcome.error}) — try again shortly.`
|
|
490
|
+
: sessionEndedMessage(outcome),
|
|
491
|
+
);
|
|
467
492
|
process.exit(1);
|
|
468
493
|
}
|
|
469
494
|
console.log(JSON.stringify(redactForDisplay(me), null, 2));
|
|
@@ -491,21 +516,19 @@ async function cmdDoctor() {
|
|
|
491
516
|
const auth = loadAuth(OMNI_DIR);
|
|
492
517
|
if (auth) {
|
|
493
518
|
try {
|
|
494
|
-
const { me } = await fetchMeWithRefresh(
|
|
519
|
+
const { me, outcome } = await fetchMeWithRefresh();
|
|
495
520
|
if (me) {
|
|
496
521
|
const label =
|
|
497
522
|
(me && typeof me === "object" && (me.device_name || me.email || me.user_id || me.id)) ||
|
|
498
523
|
"device recognized";
|
|
499
524
|
ok("auth", true, `signed in (${label})`);
|
|
525
|
+
} else if (outcome?.status === "transient") {
|
|
526
|
+
ok("auth", false, `cannot verify session (${outcome.error})`);
|
|
500
527
|
} else {
|
|
501
|
-
ok("auth", false,
|
|
528
|
+
ok("auth", false, sessionEndedMessage(outcome).replace(/^Omnirush: /, ""));
|
|
502
529
|
}
|
|
503
530
|
} catch (e) {
|
|
504
|
-
|
|
505
|
-
ok("auth", false, "session expired — run `omnirush login` again");
|
|
506
|
-
} else {
|
|
507
|
-
ok("auth", false, `cannot verify session (${e.message})`);
|
|
508
|
-
}
|
|
531
|
+
ok("auth", false, `cannot verify session (${e.message})`);
|
|
509
532
|
}
|
|
510
533
|
} else {
|
|
511
534
|
const pending = loadPendingFlow(OMNI_DIR);
|
|
@@ -706,6 +729,9 @@ function childEnv(auth) {
|
|
|
706
729
|
const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
|
|
707
730
|
gateway.replace(/\/+$/, "").replace(/\/v1$/, ""); // provider base -> manager origin
|
|
708
731
|
return {
|
|
732
|
+
// Bootstrap values only: the agent (and every sub-agent it spawns)
|
|
733
|
+
// re-reads the shared auth file in OMNIRUSH_DIR for each request, so
|
|
734
|
+
// these never go stale in a way that matters.
|
|
709
735
|
OMNIRUSH_TOKEN: auth.accessToken,
|
|
710
736
|
OMNIRUSH_ACCESS_TOKEN: auth.accessToken,
|
|
711
737
|
OMNIRUSH_ORIGIN: origin,
|
|
@@ -790,7 +816,8 @@ async function cmdRun() {
|
|
|
790
816
|
const piArgs = withModelDefaults([], resolveModelDefaults(process.env));
|
|
791
817
|
spawnAgent(cmd, [...args, ...piArgs], {
|
|
792
818
|
stdio: "inherit",
|
|
793
|
-
|
|
819
|
+
// The pair may have rotated during launch (model catalog refresh).
|
|
820
|
+
env: { ...process.env, ...childEnv(loadAuth(OMNI_DIR) ?? auth) },
|
|
794
821
|
});
|
|
795
822
|
}
|
|
796
823
|
|