omnirush 0.8.1 → 0.8.3

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.
@@ -89,6 +89,9 @@ const ENVELOPE_WRITE_CHUNK_BYTES = 64 * 1024;
89
89
  const MAX_CHANGE_JOURNAL_ENTRIES = 512;
90
90
  const MAX_CHANGE_JOURNAL_BYTES = 768 * 1024;
91
91
  const MAX_SESSION_LEDGER_ENTRIES = 512;
92
+ const SESSION_LEDGER_LOCK_TIMEOUT_MS = 5_000;
93
+ const SESSION_LEDGER_LOCK_STALE_MS = 60_000;
94
+ const SESSION_LEDGER_LOCK_RETRY_MS = 25;
92
95
  const MAX_TOUCHED_PATHS = 128;
93
96
  const MAX_GIT_STATUS_ENTRIES = 500;
94
97
  const MAX_GIT_RECENT_COMMITS = 50;
@@ -1762,6 +1765,9 @@ async function walkFallback(root: string, directory = root, rules: string[] = []
1762
1765
  } catch {
1763
1766
  return listing;
1764
1767
  }
1768
+ // Filesystem enumeration order is not stable across platforms. Keep the
1769
+ // manifest and its downstream change decisions deterministic.
1770
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1765
1771
  for (const entry of entries) {
1766
1772
  if (listing.paths.length >= MAX_FILES) break;
1767
1773
  const fullPath = resolve(directory, entry.name);
@@ -1787,6 +1793,7 @@ function filterListing(candidates: string[]): WorkspaceListing {
1787
1793
  if (isCollectorPathDenied(path)) listing.denied += 1;
1788
1794
  else if (listing.paths.length < MAX_FILES) listing.paths.push(path);
1789
1795
  }
1796
+ listing.paths.sort((left, right) => left.localeCompare(right));
1790
1797
  return listing;
1791
1798
  }
1792
1799
 
@@ -2977,6 +2984,44 @@ async function writeFileAtomic(path: string, data: Buffer | string): Promise<voi
2977
2984
  }
2978
2985
  }
2979
2986
 
2987
+ async function withSessionLedgerLock<T>(lockPath: string, task: () => Promise<T>): Promise<T> {
2988
+ const started = Date.now();
2989
+ await mkdir(dirname(lockPath), { recursive: true, mode: 0o700 });
2990
+ let handle: Awaited<ReturnType<typeof open>> | null = null;
2991
+ while (!handle) {
2992
+ try {
2993
+ handle = await open(lockPath, "wx", 0o600);
2994
+ await handle.writeFile(`${process.pid}\n`);
2995
+ } catch (error) {
2996
+ await handle?.close().catch(() => undefined);
2997
+ handle = null;
2998
+ const code = typeof error === "object" && error !== null && "code" in error
2999
+ ? (error as { code?: string }).code
3000
+ : undefined;
3001
+ if (code !== "EEXIST") throw error;
3002
+ try {
3003
+ const info = await stat(lockPath);
3004
+ if (Date.now() - info.mtimeMs > SESSION_LEDGER_LOCK_STALE_MS) {
3005
+ await rm(lockPath, { force: true });
3006
+ continue;
3007
+ }
3008
+ } catch {
3009
+ // The owner may have released the lock between EEXIST and stat.
3010
+ }
3011
+ if (Date.now() - started >= SESSION_LEDGER_LOCK_TIMEOUT_MS) {
3012
+ throw new Error("timed out waiting for the session ledger lock");
3013
+ }
3014
+ await new Promise<void>((resolvePromise) => setTimeout(resolvePromise, SESSION_LEDGER_LOCK_RETRY_MS));
3015
+ }
3016
+ }
3017
+ try {
3018
+ return await task();
3019
+ } finally {
3020
+ await handle.close().catch(() => undefined);
3021
+ await rm(lockPath, { force: true }).catch(() => undefined);
3022
+ }
3023
+ }
3024
+
2980
3025
  function parseSpoolMeta(value: unknown): SpoolMeta | null {
2981
3026
  if (!value || typeof value !== "object") return null;
2982
3027
  const record = value as Partial<SpoolMeta>;
@@ -3105,19 +3150,27 @@ export class WorkspaceCollector {
3105
3150
  this.ledger = await readSessionLedger(this.ledgerPath);
3106
3151
  }
3107
3152
 
3108
- private async saveLedger(): Promise<void> {
3153
+ private async saveLedger(sessionId: string): Promise<void> {
3109
3154
  if (!this.ledgerPath) return;
3110
3155
  await this.ledgerReady;
3111
- const entries = Object.entries(this.ledger.sessions)
3112
- .sort(([, left], [, right]) => right.lastSeenAt.localeCompare(left.lastSeenAt))
3113
- .slice(0, MAX_SESSION_LEDGER_ENTRIES);
3114
- this.ledger.sessions = Object.fromEntries(entries);
3115
- const snapshot = JSON.stringify(this.ledger);
3156
+ const record = this.ledger.sessions[sessionId]
3157
+ ? JSON.parse(JSON.stringify(this.ledger.sessions[sessionId])) as SessionLedgerRecord
3158
+ : undefined;
3116
3159
  this.ledgerWriteTail = this.ledgerWriteTail
3117
3160
  .catch(() => undefined)
3118
3161
  .then(async () => {
3119
- await mkdir(dirname(this.ledgerPath!), { recursive: true, mode: 0o700 });
3120
- await writeFileAtomic(this.ledgerPath!, snapshot);
3162
+ await withSessionLedgerLock(`${this.ledgerPath!}.lock`, async () => {
3163
+ // Reload while holding the lock so concurrent processes merge their
3164
+ // session record instead of overwriting one another's progress.
3165
+ const merged = await readSessionLedger(this.ledgerPath);
3166
+ if (record) merged.sessions[sessionId] = record;
3167
+ const entries = Object.entries(merged.sessions)
3168
+ .sort(([, left], [, right]) => right.lastSeenAt.localeCompare(left.lastSeenAt))
3169
+ .slice(0, MAX_SESSION_LEDGER_ENTRIES);
3170
+ merged.sessions = Object.fromEntries(entries);
3171
+ this.ledger = merged;
3172
+ await writeFileAtomic(this.ledgerPath!, JSON.stringify(merged));
3173
+ });
3121
3174
  });
3122
3175
  await this.ledgerWriteTail;
3123
3176
  }
@@ -3158,13 +3211,13 @@ export class WorkspaceCollector {
3158
3211
  this.appendTrace(state, "session.resumed", { session_segment: state.segment, previous_segment: previous?.segment ?? null });
3159
3212
  }
3160
3213
  this.ledger.sessions[state.id] = this.ledgerRecord(state);
3161
- await this.saveLedger();
3214
+ await this.saveLedger(state.id);
3162
3215
  }
3163
3216
 
3164
3217
  private async persistSession(state: SessionState): Promise<void> {
3165
3218
  await this.ledgerReady;
3166
3219
  this.ledger.sessions[state.id] = this.ledgerRecord(state);
3167
- await this.saveLedger();
3220
+ await this.saveLedger(state.id);
3168
3221
  }
3169
3222
 
3170
3223
  private async recordLedgerOutcome(sessionId: string, outcome: "success" | "failure"): Promise<void> {
@@ -3188,7 +3241,7 @@ export class WorkspaceCollector {
3188
3241
  record.lastFailureAt = now;
3189
3242
  }
3190
3243
  record.lastSeenAt = now;
3191
- await this.saveLedger();
3244
+ await this.saveLedger(sessionId);
3192
3245
  }
3193
3246
 
3194
3247
  async sessionCheckpoint(sessionId: string): Promise<{ resumed: boolean; segment?: number; lastMessageId?: string }> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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
@@ -577,7 +577,7 @@ async function cmdDoctor() {
577
577
  } else {
578
578
  console.log(
579
579
  `WARN agent core — installed core is v${coreVersion}, this omnirush expects v${own} ` +
580
- "(stale install: pi.dev update banners, missing omnirush provider). Fix: omnirush update",
580
+ "(stale install: agent update banners, missing omnirush provider). Fix: omnirush update",
581
581
  );
582
582
  }
583
583
  }
package/src/lib.js CHANGED
@@ -273,7 +273,7 @@ export function catalogFromGateway(payload, shipped = MODELS) {
273
273
  }
274
274
 
275
275
  /**
276
- * Prepend `--provider`/`--model` defaults for agent runs, without
276
+ * Prepend the product's provider/model/scope defaults for agent runs, without
277
277
  * duplicating flags the user already specified. Consumption rules mirror
278
278
  * pi's own parseArgs so a value token is never mistaken for a flag (e.g.
279
279
  * `omnirush -p "how do I use --model flags"` must not suppress defaults).
@@ -289,6 +289,7 @@ export function withModelDefaults(args, defaults) {
289
289
  }
290
290
  let hasProvider = false;
291
291
  let hasModel = false;
292
+ let hasModelsScope = false;
292
293
  for (let i = 0; i < args.length; i++) {
293
294
  const arg = args[i];
294
295
  if (arg === "--") break;
@@ -305,10 +306,17 @@ export function withModelDefaults(args, defaults) {
305
306
  hasModel = true;
306
307
  i++;
307
308
  }
309
+ } else if (arg === "--models") {
310
+ if (i + 1 < args.length) {
311
+ hasModelsScope = true;
312
+ i++;
313
+ }
308
314
  } else if (arg.startsWith("--provider=")) {
309
315
  hasProvider = true;
310
316
  } else if (arg.startsWith("--model=")) {
311
317
  hasModel = true;
318
+ } else if (arg.startsWith("--models=")) {
319
+ hasModelsScope = true;
312
320
  } else if (arg === "--print" || arg === "-p") {
313
321
  const next = args[i + 1];
314
322
  if (
@@ -334,6 +342,10 @@ export function withModelDefaults(args, defaults) {
334
342
  }
335
343
  }
336
344
  const out = [];
345
+ // The upstream core merges every configured provider into its registry.
346
+ // Keep BYOK configuration on disk, but scope this product's selector to
347
+ // Omnirush unless the caller explicitly opts into another scope.
348
+ if (!hasModelsScope) out.push("--models", `${DEFAULT_PROVIDER}/*`);
337
349
  if (!hasProvider) out.push("--provider", provider);
338
350
  if (!hasModel) out.push("--model", model);
339
351
  return [...out, ...args];