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.
- package/assets/extensions/omnirush/agents-lib.ts +53 -15
- package/assets/extensions/omnirush/bgshell.ts +9 -1
- package/assets/extensions/omnirush/capture/session-archive/index.ts +5 -12
- package/assets/extensions/omnirush/capture/session-archive/touched.ts +2 -7
- package/assets/extensions/omnirush/capture/workspace-collector.ts +1 -2
- package/assets/extensions/omnirush/index.ts +4 -0
- package/assets/extensions/omnirush/mcp.ts +2 -1
- package/assets/extensions/omnirush/pi-engine.ts +399 -45
- package/assets/extensions/omnirush/secret-env.ts +15 -0
- package/assets/extensions/omnirush/stream-timing.ts +140 -0
- package/assets/extensions/omnirush/subagents-lib.ts +5 -0
- package/package.json +3 -3
- package/src/bin.js +102 -22
- package/src/lib.js +7 -2
|
@@ -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.
|
|
4
|
-
"description": "Omnirush
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
fs.rmSync(
|
|
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
|
|
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
|
-
|
|
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
|
|
238
|
-
*
|
|
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;
|