omnirush 0.8.5 → 0.9.0
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 +85 -0
- package/assets/collect-once.ts +6 -5
- package/assets/extensions/omnirush/agents-lib.ts +196 -12
- package/assets/extensions/omnirush/agents.ts +97 -105
- package/assets/extensions/omnirush/auth.js +399 -115
- package/assets/extensions/omnirush/bgshell-lib.ts +400 -0
- package/assets/extensions/omnirush/bgshell.ts +392 -0
- package/assets/extensions/omnirush/collector.ts +103 -16
- package/assets/extensions/omnirush/commands.ts +14 -15
- 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 +12 -0
- package/assets/extensions/omnirush/pi-engine.ts +65 -1
- package/assets/extensions/omnirush/sota.ts +104 -47
- package/assets/extensions/omnirush/status-lib.ts +3 -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 +6 -2
- package/scripts/brand-engine.js +526 -0
- package/scripts/build-all-packages.py +29 -1
- package/scripts/smoke-packages.py +32 -1
- package/src/bin.js +252 -53
- package/src/compat.js +272 -0
- package/src/lib.js +64 -0
- package/scripts/patch-pi-branding.js +0 -251
|
@@ -36,10 +36,13 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
36
36
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
37
37
|
import { Type } from "typebox";
|
|
38
38
|
|
|
39
|
+
import { deliveryHub } from "./deliveries";
|
|
40
|
+
import { subagentTasks } from "./subagents";
|
|
39
41
|
import {
|
|
40
42
|
AGENT_ROLES,
|
|
41
43
|
AGENTS_HOOK,
|
|
42
44
|
AgentManager,
|
|
45
|
+
agentStatusSummary,
|
|
43
46
|
type AgentRunner,
|
|
44
47
|
type AgentsHook,
|
|
45
48
|
type AgentRecord,
|
|
@@ -48,9 +51,11 @@ import {
|
|
|
48
51
|
renderChildResults,
|
|
49
52
|
renderDelivery,
|
|
50
53
|
runChildAgent,
|
|
54
|
+
childModelFallback,
|
|
51
55
|
type ChildResult,
|
|
52
56
|
type ChildTask,
|
|
53
57
|
} from "./agents-lib";
|
|
58
|
+
import { SWARM_GUIDELINE } from "./swarm-lib";
|
|
54
59
|
|
|
55
60
|
/** A timeout_minutes below this is taken for a placeholder, not a limit. */
|
|
56
61
|
const MIN_TIMEOUT_MINUTES = 1;
|
|
@@ -66,7 +71,7 @@ const SpawnAgentsParams = Type.Object({
|
|
|
66
71
|
}),
|
|
67
72
|
task: Type.String({ description: "Self-contained task description for the child agent (it sees ONLY this, not your conversation)" }),
|
|
68
73
|
model: Type.Optional(Type.String({
|
|
69
|
-
description: '
|
|
74
|
+
description: 'Optional model[:effort] for THIS task (e.g. "muse-spark-1.3", "muse-spark-1.2-contributor" for cheap swarm workers, "meta-muse-spark", "muse-spark-1.1:low", "gpt-6-astra", "gpt-6-sol:high", "gpt-5.6-sol"). Omit (or leave empty) for the sub-agent model the user picked with /subagents, else the parent session\'s current model and effort',
|
|
70
75
|
})),
|
|
71
76
|
}),
|
|
72
77
|
{ description: "Tasks to delegate; they all run in parallel", minItems: 1 },
|
|
@@ -99,45 +104,35 @@ function renderSome(records: AgentRecord[], unknown: string[] = []): string {
|
|
|
99
104
|
const open = records.filter((record) => !record.result);
|
|
100
105
|
const parts: string[] = [];
|
|
101
106
|
if (finished.length > 0) parts.push(renderChildResults(finished.map((record) => record.result as ChildResult), finished.map((record) => record.id)));
|
|
102
|
-
if (open.length > 0) parts.push(`
|
|
107
|
+
if (open.length > 0) parts.push(`Open sub-agents:\n${renderAgentStatus(open)}`);
|
|
103
108
|
if (unknown.length > 0) parts.push(`Unknown agent ids: ${unknown.join(", ")}`);
|
|
104
109
|
return parts.join("\n\n") || "No matching sub-agents.";
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {}) {
|
|
108
113
|
const watchdogMinutes = Math.round(childInactivityMs() / 60_000);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let
|
|
112
|
-
|
|
113
|
-
let busy = false;
|
|
114
|
-
let oneShot = false;
|
|
114
|
+
const hub = deliveryHub(pi);
|
|
115
|
+
/** The last context seen (the TUI status bar for running agents). */
|
|
116
|
+
let lastContext: any = null;
|
|
117
|
+
let statusTimer: ReturnType<typeof setTimeout> | null = null;
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
// Busy: into the running turn at its next step. Idle: a new turn.
|
|
125
|
-
{ triggerTurn: true, deliverAs: "steer" },
|
|
126
|
-
);
|
|
127
|
-
};
|
|
119
|
+
function publishStatus(): void {
|
|
120
|
+
statusTimer = null;
|
|
121
|
+
const session = hub.currentSession;
|
|
122
|
+
if (!session || !lastContext?.ui?.setStatus) return;
|
|
123
|
+
const records = manager.list(session);
|
|
124
|
+
if (records.length > 0) lastContext.ui.setStatus("omnirush-agents", agentStatusSummary(records));
|
|
125
|
+
}
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
for (const group of manager.takeDeliverable(currentSession)) send(group.records);
|
|
136
|
-
};
|
|
127
|
+
function scheduleStatus(): void {
|
|
128
|
+
if (statusTimer) return;
|
|
129
|
+
statusTimer = setTimeout(publishStatus, 250);
|
|
130
|
+
statusTimer.unref?.();
|
|
131
|
+
}
|
|
137
132
|
|
|
138
133
|
const manager = new AgentManager({
|
|
139
134
|
run: options.run ?? ((task, options) => runChildAgent(task, {
|
|
140
|
-
cwd: options.cwd ?? cwd,
|
|
135
|
+
cwd: options.cwd ?? hub.cwd,
|
|
141
136
|
parentSessionId: options.parentSessionId,
|
|
142
137
|
sessionId: options.sessionId,
|
|
143
138
|
signal: options.signal,
|
|
@@ -145,7 +140,19 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
145
140
|
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
146
141
|
})),
|
|
147
142
|
// After the settle's own bookkeeping (waiters took theirs).
|
|
148
|
-
onDeliverable: () =>
|
|
143
|
+
onDeliverable: () => hub.notify(),
|
|
144
|
+
onChanged: scheduleStatus,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
hub.addSource({
|
|
148
|
+
hasPending: (session) => manager.hasPending(session),
|
|
149
|
+
// One message per finished batch (or per child with notify "each").
|
|
150
|
+
take: (session) => manager.takeDeliverable(session).map(({ records }) => ({
|
|
151
|
+
customType: DELIVERY_TYPE,
|
|
152
|
+
content: renderDelivery(records),
|
|
153
|
+
details: { results: records.map(resultOf) },
|
|
154
|
+
})),
|
|
155
|
+
changed: () => manager.changed(),
|
|
149
156
|
});
|
|
150
157
|
|
|
151
158
|
const hook: AgentsHook = {
|
|
@@ -157,11 +164,8 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
157
164
|
(globalThis as any)[AGENTS_HOOK] = hook;
|
|
158
165
|
|
|
159
166
|
const sessionOf = (ctx: any): string => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (ctx?.cwd) cwd = String(ctx.cwd);
|
|
163
|
-
if (ctx?.mode === "print" || ctx?.mode === "json") oneShot = true;
|
|
164
|
-
return id;
|
|
167
|
+
lastContext = ctx ?? lastContext;
|
|
168
|
+
return hub.sessionOf(ctx);
|
|
165
169
|
};
|
|
166
170
|
|
|
167
171
|
pi.registerTool({
|
|
@@ -186,25 +190,33 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
186
190
|
"Short fan-outs whose results you need right away (quick searches, lookups): the default blocking call.",
|
|
187
191
|
"Long work (implementation chunks, builds, test suites, long research — anything that may take many minutes) while you have other things to do: spawn_agents with wait:false, then keep working; the results arrive on their own as a message, so do not poll agents_status in a loop. Call agents_wait only when you have nothing else to do and need the results before going on.",
|
|
188
192
|
"Do not use spawn_agents for a single quick action — doing it yourself is cheaper.",
|
|
193
|
+
SWARM_GUIDELINE,
|
|
189
194
|
],
|
|
190
195
|
parameters: SpawnAgentsParams,
|
|
191
196
|
|
|
192
197
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
198
|
+
const parentSessionId = sessionOf(ctx);
|
|
193
199
|
const tasks: ChildTask[] = [];
|
|
194
200
|
for (const raw of params.tasks ?? []) {
|
|
195
201
|
if (!raw || typeof raw.task !== "string" || !raw.task.trim()) {
|
|
196
202
|
throw new Error("invalid tasks: every entry needs a non-empty task string");
|
|
197
203
|
}
|
|
198
|
-
//
|
|
199
|
-
|
|
204
|
+
// A task's own model[:effort] wins; an omitted (or empty) one takes
|
|
205
|
+
// the /subagents pick, else the parent session's current model and
|
|
206
|
+
// effort (resolved below by subagentTasks).
|
|
207
|
+
const requestedModel = typeof raw.model === "string" ? raw.model.trim() : "";
|
|
200
208
|
tasks.push({
|
|
201
209
|
role: raw.role,
|
|
202
210
|
task: raw.task.trim(),
|
|
203
|
-
...(
|
|
211
|
+
...(requestedModel ? { model: requestedModel } : {}),
|
|
204
212
|
});
|
|
205
213
|
}
|
|
206
214
|
if (tasks.length === 0) throw new Error("no tasks given");
|
|
207
|
-
|
|
215
|
+
// The model and effort each child runs on (subagents.ts): the task's
|
|
216
|
+
// own model[:effort], else the /subagents pick, else the parent
|
|
217
|
+
// session's current model and effort; the main model when a pick is
|
|
218
|
+
// unavailable.
|
|
219
|
+
const resolvedTasks = subagentTasks(pi, ctx, tasks);
|
|
208
220
|
if (!parentSessionId) throw new Error("no parent session id — subagents cannot be traced");
|
|
209
221
|
const workdir = ctx?.cwd ? String(ctx.cwd) : process.cwd();
|
|
210
222
|
// Only a deliberate limit counts: models fill optional numbers with
|
|
@@ -212,32 +224,42 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
212
224
|
const timeoutMs = typeof params.timeout_minutes === "number" && params.timeout_minutes >= MIN_TIMEOUT_MINUTES
|
|
213
225
|
? params.timeout_minutes * 60_000
|
|
214
226
|
: undefined;
|
|
215
|
-
// No cap: every task runs at once unless the caller
|
|
216
|
-
|
|
227
|
+
// No cap: every task runs at once unless the caller deliberately
|
|
228
|
+
// supplies max_parallel.
|
|
229
|
+
const hasParallelLimit = typeof params.max_parallel === "number" && params.max_parallel >= 1;
|
|
230
|
+
const concurrency = hasParallelLimit
|
|
217
231
|
? Math.floor(params.max_parallel)
|
|
218
|
-
:
|
|
232
|
+
: undefined;
|
|
219
233
|
const background = params.wait === false;
|
|
220
234
|
|
|
221
|
-
|
|
222
|
-
const dispatched = manager.dispatch(parentSessionId, tasks, {
|
|
235
|
+
const dispatched = manager.dispatch(parentSessionId, resolvedTasks, {
|
|
223
236
|
background,
|
|
224
237
|
concurrency,
|
|
225
238
|
notify: params.notify === "each" ? "each" : "batch",
|
|
226
239
|
...(background ? {} : { signal }),
|
|
227
240
|
onSettled: (record) => {
|
|
228
|
-
settledCount += 1;
|
|
229
241
|
if (background) return;
|
|
230
242
|
onUpdate?.({
|
|
231
|
-
content: [{ type: "text", text:
|
|
243
|
+
content: [{ type: "text", text: `${agentStatusSummary(dispatched.records)} (latest: ${record.id} ${record.role})` }],
|
|
232
244
|
details: { results: dispatched.records.filter((candidate) => candidate.result).map(resultOf) },
|
|
233
245
|
});
|
|
234
246
|
},
|
|
247
|
+
onChanged: () => {
|
|
248
|
+
if (background) return;
|
|
249
|
+
onUpdate?.({
|
|
250
|
+
content: [{ type: "text", text: agentStatusSummary(manager.list(parentSessionId)) }],
|
|
251
|
+
details: { results: manager.list(parentSessionId).filter((candidate) => candidate.result).map(resultOf) },
|
|
252
|
+
});
|
|
253
|
+
},
|
|
235
254
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
236
255
|
cwd: workdir,
|
|
237
256
|
});
|
|
238
257
|
|
|
239
258
|
if (background) {
|
|
240
|
-
const lines = dispatched.records.map((record) =>
|
|
259
|
+
const lines = dispatched.records.map((record, index) => {
|
|
260
|
+
const note = childModelFallback(resolvedTasks[index])?.note;
|
|
261
|
+
return `- ${record.id}: ${record.role}${record.model ? ` [${record.model}${record.effort ? ` · ${record.effort}` : ""}]` : ""} — ${record.task.length > 100 ? `${record.task.slice(0, 99)}…` : record.task}${note ? ` (${note})` : ""}`;
|
|
262
|
+
});
|
|
241
263
|
return {
|
|
242
264
|
content: [{
|
|
243
265
|
type: "text",
|
|
@@ -254,14 +276,20 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
254
276
|
details: {
|
|
255
277
|
background: true,
|
|
256
278
|
batch: dispatched.batch,
|
|
257
|
-
results: dispatched.records.map((record) =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
279
|
+
results: dispatched.records.map((record, index) => {
|
|
280
|
+
const modelFallback = childModelFallback(resolvedTasks[index]);
|
|
281
|
+
return {
|
|
282
|
+
agent_id: record.id,
|
|
283
|
+
role: record.role,
|
|
284
|
+
task: record.task,
|
|
285
|
+
session_id: record.sessionId,
|
|
286
|
+
...(record.model ? { model: record.model } : {}),
|
|
287
|
+
...(record.effort ? { effort: record.effort } : {}),
|
|
288
|
+
...(modelFallback ? { model_fallback: modelFallback } : {}),
|
|
289
|
+
parallel_limit: record.parallelLimit,
|
|
290
|
+
status: "running",
|
|
291
|
+
};
|
|
292
|
+
}),
|
|
265
293
|
},
|
|
266
294
|
};
|
|
267
295
|
}
|
|
@@ -286,7 +314,20 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
286
314
|
const text = renderAgentStatus(records) + (unknown.length ? `\nUnknown agent ids: ${unknown.join(", ")}` : "");
|
|
287
315
|
return {
|
|
288
316
|
content: [{ type: "text", text }],
|
|
289
|
-
details: {
|
|
317
|
+
details: {
|
|
318
|
+
agents: records.map((record) => ({
|
|
319
|
+
agent_id: record.id,
|
|
320
|
+
status: record.status,
|
|
321
|
+
role: record.role,
|
|
322
|
+
task: record.task,
|
|
323
|
+
session_id: record.sessionId,
|
|
324
|
+
...(record.model ? { model: record.model } : {}),
|
|
325
|
+
parallel_limit: record.parallelLimit,
|
|
326
|
+
turns: record.turns,
|
|
327
|
+
tools_running: record.toolsRunning,
|
|
328
|
+
last_activity_at: record.lastActivityAt,
|
|
329
|
+
})),
|
|
330
|
+
},
|
|
290
331
|
};
|
|
291
332
|
},
|
|
292
333
|
});
|
|
@@ -356,55 +397,6 @@ export default function (pi: ExtensionAPI, options: { run?: AgentRunner } = {})
|
|
|
356
397
|
},
|
|
357
398
|
});
|
|
358
399
|
|
|
359
|
-
pi.on("session_start", (_event: any, ctx: any) => {
|
|
360
|
-
sessionOf(ctx);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
pi.on("agent_start", (_event: any, ctx: any) => {
|
|
364
|
-
sessionOf(ctx);
|
|
365
|
-
busy = true;
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* One-shot runs (print/json mode) end when the agent settles: background
|
|
370
|
-
* children still out would be lost. The run waits for them here and
|
|
371
|
-
* continues with their results (a new run nested in this settle, whose
|
|
372
|
-
* own settle resumes this one when everything is done).
|
|
373
|
-
*/
|
|
374
|
-
let resumeOuter: (() => void) | null = null;
|
|
375
|
-
pi.on("agent_settled", async (_event: any, ctx: any) => {
|
|
376
|
-
busy = false;
|
|
377
|
-
const session = sessionOf(ctx);
|
|
378
|
-
if (!oneShot) {
|
|
379
|
-
// Anything that finished right as the run ended.
|
|
380
|
-
deliver();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
const outer = resumeOuter;
|
|
384
|
-
resumeOuter = null;
|
|
385
|
-
try {
|
|
386
|
-
for (;;) {
|
|
387
|
-
if (typeof ctx?.isIdle === "function" && !ctx.isIdle()) {
|
|
388
|
-
// A run started (a delivery): its settle resumes this one.
|
|
389
|
-
await new Promise<void>((resolve) => { resumeOuter = resolve; });
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
if (!session || !manager.hasPending(session)) break;
|
|
393
|
-
const groups = manager.takeDeliverable(session);
|
|
394
|
-
if (groups.length === 0) {
|
|
395
|
-
await manager.changed();
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
busy = true;
|
|
399
|
-
const records = groups.flatMap((group) => group.records);
|
|
400
|
-
send(records);
|
|
401
|
-
if (typeof ctx?.isIdle === "function" && ctx.isIdle()) busy = false;
|
|
402
|
-
}
|
|
403
|
-
} finally {
|
|
404
|
-
outer?.();
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
|
|
408
400
|
pi.on("session_shutdown", async () => {
|
|
409
401
|
// Children the session ends under are stopped (the collector, when it
|
|
410
402
|
// runs, already did this before its last capture).
|