omnirush 0.9.1 → 0.10.1

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.
@@ -0,0 +1,140 @@
1
+ // When things happened inside a run, as the 2.x engine records them and pi
2
+ // does not: when each content block of a model step started and ended
3
+ // streaming (text, thinking, tool call), when the step's response ended,
4
+ // when each tool call started running, and when the run went idle and how
5
+ // it ended. pi keeps only one timestamp per message.
6
+ //
7
+ // Heard from pi's live events and written into the session itself, as
8
+ // `omnirush-timing` entries (custom entries: never part of the model's
9
+ // context), so every process records its own, a sub-agent's included, and
10
+ // a session read back later (a resumed one, a sub-agent's file) still has
11
+ // them. pi-engine.ts reads them into the trace's 2.x fields:
12
+ //
13
+ // one entry per model step, at the end of its turn
14
+ // {step: <the assistant message's timestamp>, streamed, blocks:
15
+ // [{index, created, completed}], tools: {<toolCallId>: ran}}
16
+ // one entry when the run settles
17
+ // {idle: {at, outcome: "succeeded" | "failed" | "interrupted"}}
18
+ //
19
+ // Loaded before the collector (index.ts): the idle entry is on the branch
20
+ // when the collector reads the settled run.
21
+
22
+ import { TIMING_ENTRY_TYPE } from "./pi-engine";
23
+
24
+ type Block = { index: number; created?: number; completed?: number };
25
+ type Step = { streamed?: number; blocks: Map<number, Block>; message?: any };
26
+
27
+ const STARTS = new Set(["text_start", "thinking_start", "toolcall_start"]);
28
+ const ENDS = new Set(["text_end", "thinking_end", "toolcall_end"]);
29
+
30
+ /** A run's outcome from its last model step (2.x idle marker). */
31
+ export function outcomeOf(stopReason: unknown): "succeeded" | "failed" | "interrupted" {
32
+ if (stopReason === "stop" || stopReason === "length") return "succeeded";
33
+ if (stopReason === "error") return "failed";
34
+ return "interrupted";
35
+ }
36
+
37
+ export default function recordStreamTiming(pi: any, now: () => number = Date.now): void {
38
+ const steps = new Map<number, Step>();
39
+ const ran = new Map<string, number>();
40
+ let running = false;
41
+ let lastStop: unknown = undefined;
42
+
43
+ const append = (data: Record<string, unknown>) => {
44
+ try {
45
+ pi.appendEntry(TIMING_ENTRY_TYPE, data);
46
+ } catch {
47
+ /* never in the run's way */
48
+ }
49
+ };
50
+
51
+ const stepOf = (message: any): Step | null => {
52
+ const key = Number(message?.timestamp);
53
+ if (message?.role !== "assistant" || !Number.isFinite(key)) return null;
54
+ let step = steps.get(key);
55
+ if (!step) {
56
+ step = { blocks: new Map() };
57
+ steps.set(key, step);
58
+ }
59
+ return step;
60
+ };
61
+
62
+ /** The step of `message` (a finished assistant message) as its timing entry. */
63
+ const flushStep = (message: any) => {
64
+ const key = Number(message?.timestamp);
65
+ const step = steps.get(key);
66
+ if (!step || message?.role !== "assistant") return;
67
+ steps.delete(key);
68
+ const tools: Record<string, number> = {};
69
+ for (const block of Array.isArray(message.content) ? message.content : []) {
70
+ const id = block?.type === "toolCall" && typeof block.id === "string" ? block.id : "";
71
+ const at = id ? ran.get(id) : undefined;
72
+ if (at !== undefined) {
73
+ tools[id] = at;
74
+ ran.delete(id);
75
+ }
76
+ }
77
+ const blocks = [...step.blocks.values()].sort((a, b) => a.index - b.index);
78
+ append({
79
+ step: key,
80
+ ...(step.streamed !== undefined ? { streamed: step.streamed } : {}),
81
+ ...(blocks.length > 0 ? { blocks } : {}),
82
+ ...(Object.keys(tools).length > 0 ? { tools } : {}),
83
+ });
84
+ };
85
+
86
+ const settle = (outcome: "succeeded" | "failed" | "interrupted") => {
87
+ if (!running) return;
88
+ running = false;
89
+ // Steps whose turn never ended (an aborted run) keep their timing too.
90
+ for (const step of [...steps.values()]) if (step.message) flushStep(step.message);
91
+ steps.clear();
92
+ append({ idle: { at: now(), outcome } });
93
+ lastStop = undefined;
94
+ };
95
+
96
+ pi.on("agent_start", () => {
97
+ running = true;
98
+ lastStop = undefined;
99
+ });
100
+
101
+ pi.on("message_update", (event: any) => {
102
+ const kind = event?.assistantMessageEvent?.type;
103
+ const index = event?.assistantMessageEvent?.contentIndex;
104
+ if (typeof kind !== "string" || typeof index !== "number") return;
105
+ const step = stepOf(event?.message);
106
+ if (!step) return;
107
+ if (STARTS.has(kind)) {
108
+ if (!step.blocks.has(index)) step.blocks.set(index, { index, created: now() });
109
+ } else if (ENDS.has(kind)) {
110
+ const block = step.blocks.get(index) ?? { index };
111
+ block.completed = now();
112
+ step.blocks.set(index, block);
113
+ }
114
+ });
115
+
116
+ pi.on("message_end", (event: any) => {
117
+ const step = stepOf(event?.message);
118
+ if (!step) return;
119
+ step.streamed = now();
120
+ step.message = event.message;
121
+ lastStop = event.message.stopReason;
122
+ });
123
+
124
+ pi.on("tool_execution_start", (event: any) => {
125
+ if (typeof event?.toolCallId === "string") ran.set(event.toolCallId, now());
126
+ });
127
+
128
+ pi.on("turn_end", (event: any) => {
129
+ flushStep(event?.message);
130
+ });
131
+
132
+ pi.on("agent_settled", () => {
133
+ settle(outcomeOf(lastStop));
134
+ });
135
+
136
+ // Quit, reload or another session while a run is still going.
137
+ pi.on("session_shutdown", () => {
138
+ settle("interrupted");
139
+ });
140
+ }
@@ -131,6 +131,11 @@ export function catalogFromPayload(payload: unknown): CatalogModel[] | null {
131
131
  if (!isRecord(entry) || !isModelId(entry.id)) continue;
132
132
  const id = entry.id.trim();
133
133
  if (out.some((model) => model.id === id)) continue;
134
+ const ownership = [entry.provider, entry.owned_by, entry.owner, entry.source, entry.namespace, entry.managed_by]
135
+ .filter((value): value is string => typeof value === "string")
136
+ .map((value) => value.trim().toLowerCase());
137
+ const shipped = ["gpt-6-astra", "gpt-6-sol", "gpt-5.6-sol", "meta-muse-spark", "muse-spark-1.1", "muse-spark-1.3", "muse-spark-1.2-contributor"];
138
+ if (!shipped.includes(id) && entry.omnirush !== true && !ownership.includes("omnirush")) continue;
134
139
  const levels = Array.isArray(entry.reasoning_levels)
135
140
  ? EFFORTS.filter((effort) => entry.reasoning_levels.some((level: unknown) => parseEffort(level) === effort))
136
141
  : [...staticLevels(id)];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.9.1",
4
- "description": "Omnirush \u2014 free daily tokens for the most powerful coding model on earth.",
3
+ "version": "0.10.1",
4
+ "description": "Omnirush — free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "bin": {
@@ -45,4 +45,4 @@
45
45
  "optionalDependencies": {
46
46
  "@picovoice/pvrecorder-node": "1.2.9"
47
47
  }
48
- }
48
+ }
package/src/bin.js CHANGED
@@ -160,7 +160,71 @@ function ensureDirs() {
160
160
  } catch {
161
161
  /* best effort — pi falls back to PATH, then its own downloader */
162
162
  }
163
- installDefaultThinkingLevel();
163
+ withInstallLock(installDefaultThinkingLevel);
164
+ }
165
+
166
+ const INSTALL_LOCK = path.join(OMNI_DIR, ".install.lock");
167
+ const INSTALL_LOCK_STALE_MS = 60_000;
168
+ const WINDOWS_INSTALL_BUSY = new Set(["EPERM", "EACCES", "EBUSY"]);
169
+
170
+ function sleepSync(milliseconds) {
171
+ const wait = new Int32Array(new SharedArrayBuffer(4));
172
+ Atomics.wait(wait, 0, 0, milliseconds);
173
+ }
174
+
175
+ /** Serialize launcher state changes across concurrent CLI processes. */
176
+ function withInstallLock(task) {
177
+ fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
178
+ const started = Date.now();
179
+ for (;;) {
180
+ try {
181
+ fs.mkdirSync(INSTALL_LOCK);
182
+ fs.writeFileSync(path.join(INSTALL_LOCK, "owner"), `${process.pid}\n`, { mode: 0o600 });
183
+ break;
184
+ } catch (error) {
185
+ if (error?.code !== "EEXIST") throw error;
186
+ try {
187
+ if (Date.now() - fs.statSync(INSTALL_LOCK).mtimeMs > INSTALL_LOCK_STALE_MS) {
188
+ fs.rmSync(INSTALL_LOCK, { recursive: true, force: true });
189
+ continue;
190
+ }
191
+ } catch {
192
+ continue;
193
+ }
194
+ if (Date.now() - started > 15_000) throw new Error("timed out waiting for the Omnirush installer lock");
195
+ sleepSync(25);
196
+ }
197
+ }
198
+ try {
199
+ return task();
200
+ } finally {
201
+ fs.rmSync(INSTALL_LOCK, { recursive: true, force: true });
202
+ }
203
+ }
204
+
205
+ /** Replace a state file only after its complete contents are ready. */
206
+ function writeFileAtomic(filename, contents, options = {}) {
207
+ const temporary = `${filename}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
208
+ const fd = fs.openSync(temporary, "wx", options.mode ?? 0o600);
209
+ try {
210
+ fs.writeSync(fd, contents);
211
+ try { fs.fsyncSync(fd); } catch { /* best effort on filesystems without fsync */ }
212
+ } finally {
213
+ fs.closeSync(fd);
214
+ }
215
+ try {
216
+ for (let attempt = 0; ; attempt++) {
217
+ try {
218
+ fs.renameSync(temporary, filename);
219
+ break;
220
+ } catch (error) {
221
+ if (attempt >= 9 || !WINDOWS_INSTALL_BUSY.has(error?.code)) throw error;
222
+ sleepSync(20 * (attempt + 1));
223
+ }
224
+ }
225
+ } finally {
226
+ fs.rmSync(temporary, { force: true });
227
+ }
164
228
  }
165
229
 
166
230
  /**
@@ -198,7 +262,7 @@ function installDefaultThinkingLevel() {
198
262
  return;
199
263
  }
200
264
  if (next !== settings) {
201
- fs.writeFileSync(PI_SETTINGS_JSON, JSON.stringify(next, null, 2) + "\n");
265
+ writeFileAtomic(PI_SETTINGS_JSON, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
202
266
  }
203
267
  }
204
268
 
@@ -237,7 +301,7 @@ async function launchModels(gatewayUrl, accessToken) {
237
301
  const models = catalogFromGateway(payload);
238
302
  if (models) {
239
303
  try {
240
- fs.writeFileSync(cacheFile, JSON.stringify({ gateway: base, fetched_at: new Date().toISOString(), payload }), { mode: 0o600 });
304
+ withInstallLock(() => writeFileAtomic(cacheFile, JSON.stringify({ gateway: base, fetched_at: new Date().toISOString(), payload }), { mode: 0o600 }));
241
305
  } catch {
242
306
  /* the cache is a convenience */
243
307
  }
@@ -263,18 +327,20 @@ async function launchModels(gatewayUrl, accessToken) {
263
327
  }
264
328
 
265
329
  function installModelsJson(gatewayUrl, models) {
266
- let current = {};
267
- try {
268
- current = JSON.parse(fs.readFileSync(PI_MODELS_JSON, "utf8"));
269
- } catch {
270
- /* fresh install */
271
- }
272
- if (typeof current !== "object" || current === null) current = {};
273
- current.providers = {
274
- ...(current.providers || {}),
275
- ...modelsConfig({ gatewayUrl, ...(models ? { models } : {}) }).providers,
276
- };
277
- fs.writeFileSync(PI_MODELS_JSON, JSON.stringify(current, null, 2) + "\n");
330
+ withInstallLock(() => {
331
+ let current = {};
332
+ try {
333
+ current = JSON.parse(fs.readFileSync(PI_MODELS_JSON, "utf8"));
334
+ } catch {
335
+ /* fresh install */
336
+ }
337
+ if (typeof current !== "object" || current === null) current = {};
338
+ current.providers = {
339
+ ...(current.providers || {}),
340
+ ...modelsConfig({ gatewayUrl, ...(models ? { models } : {}) }).providers,
341
+ };
342
+ writeFileAtomic(PI_MODELS_JSON, JSON.stringify(current, null, 2) + "\n", { mode: 0o600 });
343
+ });
278
344
  }
279
345
 
280
346
  function installExtensions() {
@@ -292,11 +358,25 @@ function installExtensions() {
292
358
  }
293
359
  }
294
360
  };
295
- copy(src, PI_EXT_DIR);
296
- // Modules of the earlier collector: nothing imports them any more.
297
- for (const stale of ["collector-lib.ts", "trace-format.ts"]) {
298
- fs.rmSync(path.join(PI_EXT_DIR, stale), { force: true });
299
- }
361
+ withInstallLock(() => {
362
+ const staging = path.join(path.dirname(PI_EXT_DIR), `.omnirush-extension-${process.pid}-${Date.now()}`);
363
+ const previous = `${PI_EXT_DIR}.previous-${process.pid}`;
364
+ fs.rmSync(staging, { recursive: true, force: true });
365
+ fs.rmSync(previous, { recursive: true, force: true });
366
+ copy(src, staging);
367
+ try {
368
+ if (fs.existsSync(PI_EXT_DIR)) fs.renameSync(PI_EXT_DIR, previous);
369
+ fs.renameSync(staging, PI_EXT_DIR);
370
+ fs.rmSync(previous, { recursive: true, force: true });
371
+ } catch {
372
+ // Windows can refuse a directory rename while another agent has a
373
+ // module open. Keep the running installation usable as a fallback.
374
+ fs.rmSync(staging, { recursive: true, force: true });
375
+ copy(src, PI_EXT_DIR);
376
+ } finally {
377
+ fs.rmSync(previous, { recursive: true, force: true });
378
+ }
379
+ });
300
380
  }
301
381
 
302
382
  function agentGatewayUrl(auth) {
@@ -561,7 +641,7 @@ async function cmdDoctor() {
561
641
  const gateway = agentGatewayUrl(loadAuth(OMNI_DIR));
562
642
  try {
563
643
  const res = await fetchJson(`${gateway}/models`, { timeout: 5000 });
564
- ok("manager", res.status === 401 || res.status < 500, `${origin} -> HTTP ${res.status}`);
644
+ ok("manager", res.status === 401 || res.status === 200, `${origin} -> HTTP ${res.status}`);
565
645
  } catch (e) {
566
646
  ok("manager", false, `${origin} unreachable (${e.message})`);
567
647
  }
@@ -607,7 +687,7 @@ async function cmdDoctor() {
607
687
  const r = await fetchJson("https://registry.npmjs.org/omnirush/latest", { timeout: 5000 });
608
688
  if (r.status === 200) {
609
689
  latest = JSON.parse(r.body).version;
610
- fs.writeFileSync(cacheFile, JSON.stringify({ latest, checked_at: Date.now() }));
690
+ withInstallLock(() => writeFileAtomic(cacheFile, JSON.stringify({ latest, checked_at: Date.now() }), { mode: 0o600 }));
611
691
  }
612
692
  }
613
693
  if (latest && latest !== own) {
package/src/lib.js CHANGED
@@ -234,8 +234,9 @@ const GATEWAY_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
234
234
  * The model list from the gateway's catalog (GET /v1/models, the list the
235
235
  * desktop app shows), in its order, for pi's models.json: a model the CLI
236
236
  * ships keeps its entry (pricing, effort map) under the gateway's display
237
- * name; a model it does not know gets a generic entry from the catalog's
238
- * fields (context limit, image input, reasoning). Shipped models the
237
+ * name; a future model is accepted only when the gateway explicitly marks
238
+ * it as Omnirush-owned. Arbitrary provider/BYOK entries are rejected.
239
+ * Shipped models the
239
240
  * gateway does not list (sub-agent-only Muse ids) follow, so
240
241
  * `spawn_agents` can still name them. Null when the payload is not a
241
242
  * usable catalog: the caller keeps the shipped list.
@@ -254,6 +255,10 @@ export function catalogFromGateway(payload, shipped = MODELS) {
254
255
  listed.push({ ...base, ...(name ? { name } : {}) });
255
256
  continue;
256
257
  }
258
+ const ownership = [entry.provider, entry.owned_by, entry.owner, entry.source, entry.namespace, entry.managed_by]
259
+ .filter((value) => typeof value === "string")
260
+ .map((value) => value.trim().toLowerCase());
261
+ if (entry.omnirush !== true && !ownership.includes("omnirush")) continue;
257
262
  const caps = entry.capabilities && typeof entry.capabilities === "object" ? entry.capabilities : {};
258
263
  const context = entry.limits && Number.isSafeInteger(entry.limits.context) && entry.limits.context > 0 ? entry.limits.context : 400000;
259
264
  const reasoning = caps.reasoning !== false;