omnirush 0.8.6 → 0.9.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/CHANGELOG.md +120 -0
- package/assets/extensions/omnirush/agents-lib.ts +501 -63
- package/assets/extensions/omnirush/agents.ts +140 -115
- package/assets/extensions/omnirush/bgshell-lib.ts +432 -0
- package/assets/extensions/omnirush/bgshell.ts +392 -0
- package/assets/extensions/omnirush/capture/workspace-collector.ts +86 -22
- package/assets/extensions/omnirush/collector.ts +275 -43
- package/assets/extensions/omnirush/commands.ts +2 -0
- package/assets/extensions/omnirush/deliveries.ts +145 -0
- package/assets/extensions/omnirush/guard/UPSTREAM +2 -0
- package/assets/extensions/omnirush/guard/git-command-policy.ts +885 -0
- package/assets/extensions/omnirush/guard-lib.ts +230 -0
- package/assets/extensions/omnirush/guard.ts +340 -0
- package/assets/extensions/omnirush/index.ts +32 -6
- package/assets/extensions/omnirush/mcp.ts +103 -8
- package/assets/extensions/omnirush/memory-lib.ts +516 -0
- package/assets/extensions/omnirush/pi-engine.ts +115 -3
- package/assets/extensions/omnirush/plan-lib.ts +38 -9
- package/assets/extensions/omnirush/plan.ts +85 -30
- package/assets/extensions/omnirush/sota.ts +52 -4
- package/assets/extensions/omnirush/status-lib.ts +3 -0
- package/assets/extensions/omnirush/subagent-marker.ts +21 -0
- package/assets/extensions/omnirush/subagents-lib.ts +623 -0
- package/assets/extensions/omnirush/subagents.ts +305 -0
- package/assets/extensions/omnirush/swarm-lib.ts +142 -0
- package/assets/extensions/omnirush/swarm.ts +95 -0
- package/assets/extensions/omnirush/voice/capture.ts +502 -0
- package/assets/extensions/omnirush/voice/core/UPSTREAM +16 -0
- package/assets/extensions/omnirush/voice/core/file-source.ts +70 -0
- package/assets/extensions/omnirush/voice/core/index.ts +21 -0
- package/assets/extensions/omnirush/voice/core/keyterms.ts +117 -0
- package/assets/extensions/omnirush/voice/core/resample.ts +63 -0
- package/assets/extensions/omnirush/voice/core/segmenter.ts +231 -0
- package/assets/extensions/omnirush/voice/core/session.ts +403 -0
- package/assets/extensions/omnirush/voice/core/text.ts +81 -0
- package/assets/extensions/omnirush/voice/core/transcriber.ts +135 -0
- package/assets/extensions/omnirush/voice/core/types.ts +102 -0
- package/assets/extensions/omnirush/voice/core/wav.ts +95 -0
- package/assets/extensions/omnirush/voice/keys.ts +435 -0
- package/assets/extensions/omnirush/voice/kitty.ts +64 -0
- package/assets/extensions/omnirush/voice/pvrecorder-worker.cjs +43 -0
- package/assets/extensions/omnirush/voice/settings.ts +67 -0
- package/assets/extensions/omnirush/voice.ts +838 -0
- package/assets/extensions/omnirush/yolo-lib.ts +80 -0
- package/assets/extensions/omnirush/yolo.ts +85 -0
- package/package.json +7 -3
- package/scripts/brand-engine.js +526 -0
- package/scripts/build-all-packages.py +29 -1
- package/scripts/smoke-packages.py +33 -1
- package/src/bin.js +232 -33
- package/src/compat.js +272 -0
- package/src/lib.js +64 -0
- package/src/sessions.js +222 -0
- package/scripts/patch-pi-branding.js +0 -251
|
@@ -18,10 +18,21 @@
|
|
|
18
18
|
// The MCP SDK is imported from the committed sibling bundle
|
|
19
19
|
// (mcp-client.bundle.js): pi copies this directory into
|
|
20
20
|
// ~/.pi/agent/extensions/omnirush/ and jiti there resolves only sibling
|
|
21
|
-
// files, never npm packages.
|
|
21
|
+
// files, never npm packages. It is loaded on first connect, so a process
|
|
22
|
+
// without MCP servers never pays for it.
|
|
23
|
+
//
|
|
24
|
+
// Sub-agents (spawn_agents children) do not start every server up front:
|
|
25
|
+
// each child used to spawn its own copy of every configured stdio server
|
|
26
|
+
// at session start (20 children x 3 servers = 60 extra processes). A child
|
|
27
|
+
// registers the tools from the list its top agent saved
|
|
28
|
+
// (<omnirush dir>/mcp-tools.json, per server config) and connects a server
|
|
29
|
+
// only when it first calls one of its tools; without a saved list it
|
|
30
|
+
// connects at start as before.
|
|
22
31
|
|
|
32
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
33
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
23
34
|
import path from "node:path";
|
|
24
|
-
import { Client
|
|
35
|
+
import type { Client } from "./mcp-client.bundle.js";
|
|
25
36
|
import { omniDir } from "./auth";
|
|
26
37
|
import {
|
|
27
38
|
dedupeToolNames,
|
|
@@ -53,7 +64,62 @@ const SHUTDOWN_BUDGET_MS = 3_000;
|
|
|
53
64
|
// Stderr lines kept per stdio server for failure diagnostics.
|
|
54
65
|
const STDERR_TAIL_LINES = 5;
|
|
55
66
|
|
|
56
|
-
|
|
67
|
+
/** "deferred": tools registered from the saved list, connects on first use (sub-agents). */
|
|
68
|
+
type ServerStatus = "connecting" | "connected" | "failed" | "deferred";
|
|
69
|
+
|
|
70
|
+
type McpSdk = typeof import("./mcp-client.bundle.js");
|
|
71
|
+
let sdkPromise: Promise<McpSdk> | null = null;
|
|
72
|
+
let loadedSdk: McpSdk | null = null;
|
|
73
|
+
/** The MCP SDK bundle, imported on first use. */
|
|
74
|
+
function loadSdk(): Promise<McpSdk> {
|
|
75
|
+
sdkPromise ??= import("./mcp-client.bundle.js").then((sdk) => {
|
|
76
|
+
loadedSdk = sdk;
|
|
77
|
+
return sdk;
|
|
78
|
+
});
|
|
79
|
+
return sdkPromise;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Saved tool lists (by the process that connected), keyed by server name. */
|
|
83
|
+
export const MCP_TOOLS_CACHE_FILE = "mcp-tools.json";
|
|
84
|
+
/** A saved list older than this is not trusted (the server may have changed). */
|
|
85
|
+
const TOOLS_CACHE_MAX_AGE_MS = 7 * 24 * 3600_000;
|
|
86
|
+
|
|
87
|
+
/** Identity of a server config: a saved tool list only counts for the same config. */
|
|
88
|
+
export function serverConfigKey(config: McpServerConfig): string {
|
|
89
|
+
return createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 32);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readToolsCache(dir: string): Record<string, { key: string; at: number; tools: McpToolInfo[] }> {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(readFileSync(path.join(dir, MCP_TOOLS_CACHE_FILE), "utf8"));
|
|
95
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
96
|
+
} catch {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Save one server's tool list (atomic; other servers' entries kept). */
|
|
102
|
+
function saveToolsCache(dir: string, state: ServerState): void {
|
|
103
|
+
try {
|
|
104
|
+
const cache = readToolsCache(dir);
|
|
105
|
+
cache[state.name] = { key: serverConfigKey(state.config), at: Date.now(), tools: state.tools };
|
|
106
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
107
|
+
const file = path.join(dir, MCP_TOOLS_CACHE_FILE);
|
|
108
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
109
|
+
writeFileSync(tmp, JSON.stringify(cache), { mode: 0o600 });
|
|
110
|
+
renameSync(tmp, file);
|
|
111
|
+
} catch {
|
|
112
|
+
/* only an optimization for sub-agents */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A saved tool list for this server config, or null. */
|
|
117
|
+
function cachedTools(dir: string, state: ServerState, now = Date.now()): McpToolInfo[] | null {
|
|
118
|
+
const entry = readToolsCache(dir)[state.name];
|
|
119
|
+
if (!entry || entry.key !== serverConfigKey(state.config) || !Array.isArray(entry.tools)) return null;
|
|
120
|
+
if (!(now - Number(entry.at) < TOOLS_CACHE_MAX_AGE_MS)) return null;
|
|
121
|
+
return entry.tools.filter((tool) => tool && typeof tool.name === "string");
|
|
122
|
+
}
|
|
57
123
|
|
|
58
124
|
/** OMNIRUSH_DEBUG=1 diagnostics (same convention as the sota guard). */
|
|
59
125
|
function debug(line: string): void {
|
|
@@ -79,6 +145,8 @@ interface ServerState {
|
|
|
79
145
|
tools: McpToolInfo[];
|
|
80
146
|
client?: Client;
|
|
81
147
|
stderrTail: string[];
|
|
148
|
+
/** A deferred server's connect in flight (single-flight for parallel tool calls). */
|
|
149
|
+
connecting?: Promise<void>;
|
|
82
150
|
}
|
|
83
151
|
|
|
84
152
|
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
@@ -138,6 +206,7 @@ function contentText(content: Array<Record<string, any>>): string {
|
|
|
138
206
|
|
|
139
207
|
/** Connect one server (bounded). Throws on failure — caller isolates. */
|
|
140
208
|
async function connectServer(state: ServerState): Promise<void> {
|
|
209
|
+
const { Client, StdioClientTransport, StreamableHTTPClientTransport } = await loadSdk();
|
|
141
210
|
const client = new Client({
|
|
142
211
|
name: "omnirush",
|
|
143
212
|
version: (process.env.OMNIRUSH_VERSION || "").trim() || "0.0.0",
|
|
@@ -194,6 +263,11 @@ async function connectServer(state: ServerState): Promise<void> {
|
|
|
194
263
|
}
|
|
195
264
|
}
|
|
196
265
|
|
|
266
|
+
/** A spawn_agents child process. */
|
|
267
|
+
function isSubagent(): boolean {
|
|
268
|
+
return Boolean(String(process.env.OMNIRUSH_PARENT_SESSION ?? "").trim());
|
|
269
|
+
}
|
|
270
|
+
|
|
197
271
|
function describeServer(state: ServerState): string {
|
|
198
272
|
if (state.config.url) return `${state.name} (${state.config.url})`;
|
|
199
273
|
const args = (state.config.args ?? []).join(" ");
|
|
@@ -231,6 +305,11 @@ export default function (pi: any) {
|
|
|
231
305
|
void toolCallId;
|
|
232
306
|
void onUpdate;
|
|
233
307
|
const current = servers.get(state.name);
|
|
308
|
+
if (current?.status === "deferred") {
|
|
309
|
+
// A sub-agent's first call to this server: connect now.
|
|
310
|
+
current.connecting ??= connectServer(current).finally(() => { current.connecting = undefined; });
|
|
311
|
+
await current.connecting.catch(() => undefined);
|
|
312
|
+
}
|
|
234
313
|
const client = current?.client;
|
|
235
314
|
if (current?.status !== "connected" || !client) {
|
|
236
315
|
throw new Error(
|
|
@@ -248,7 +327,7 @@ export default function (pi: any) {
|
|
|
248
327
|
{ timeout: CALL_TIMEOUT_MS, signal },
|
|
249
328
|
);
|
|
250
329
|
} catch (error: any) {
|
|
251
|
-
const detail = error instanceof McpError ? error.message : error?.message ?? String(error);
|
|
330
|
+
const detail = loadedSdk && error instanceof loadedSdk.McpError ? error.message : error?.message ?? String(error);
|
|
252
331
|
throw new Error(`MCP call failed on "${state.name}": ${detail}`);
|
|
253
332
|
}
|
|
254
333
|
const content = mcpContentToPiContent(result?.content);
|
|
@@ -284,6 +363,7 @@ export default function (pi: any) {
|
|
|
284
363
|
try {
|
|
285
364
|
await connectServer(state);
|
|
286
365
|
registerServerTools(state);
|
|
366
|
+
saveToolsCache(omniDir(), state);
|
|
287
367
|
} catch {
|
|
288
368
|
/* connectServer recorded the failure; isolation keeps going */
|
|
289
369
|
}
|
|
@@ -316,7 +396,9 @@ export default function (pi: any) {
|
|
|
316
396
|
const stateLabel =
|
|
317
397
|
state.status === "connected"
|
|
318
398
|
? `connected, ${state.tools.length} tool${state.tools.length === 1 ? "" : "s"}`
|
|
319
|
-
: state.status === "
|
|
399
|
+
: state.status === "deferred"
|
|
400
|
+
? `connects on first use, ${state.tools.length} tool${state.tools.length === 1 ? "" : "s"}`
|
|
401
|
+
: state.status === "connecting"
|
|
320
402
|
? "connecting…"
|
|
321
403
|
: `failed: ${state.error ?? "unknown error"}`;
|
|
322
404
|
lines.push(` ${describeServer(state)} — ${stateLabel}`);
|
|
@@ -367,10 +449,23 @@ export default function (pi: any) {
|
|
|
367
449
|
for (const error of errors) {
|
|
368
450
|
ctx?.ui?.notify?.(`mcp config: ${error}`, "warning");
|
|
369
451
|
}
|
|
370
|
-
if (servers.size
|
|
371
|
-
|
|
372
|
-
|
|
452
|
+
if (servers.size === 0) return;
|
|
453
|
+
if (isSubagent()) {
|
|
454
|
+
// Tools from the saved list; the server starts on first use.
|
|
455
|
+
const dir = omniDir();
|
|
456
|
+
for (const state of servers.values()) {
|
|
457
|
+
const tools = cachedTools(dir, state);
|
|
458
|
+
if (!tools) continue;
|
|
459
|
+
state.tools = tools;
|
|
460
|
+
state.status = "deferred";
|
|
461
|
+
registerServerTools(state);
|
|
462
|
+
}
|
|
463
|
+
const rest = [...servers.values()].filter((state) => state.status !== "deferred");
|
|
464
|
+
await Promise.allSettled(rest.map((state) => connectAll(state.name)));
|
|
465
|
+
return;
|
|
373
466
|
}
|
|
467
|
+
// Isolated + bounded per server; failures surface via /mcp only.
|
|
468
|
+
await connectAll();
|
|
374
469
|
});
|
|
375
470
|
|
|
376
471
|
pi.on("session_shutdown", async () => {
|