omnirush 0.10.1 → 0.10.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.
- package/assets/extensions/omnirush/agents-lib.ts +66 -11
- package/assets/extensions/omnirush/agents.ts +85 -18
- package/assets/extensions/omnirush/auth.js +3 -2
- package/assets/extensions/omnirush/bgshell.ts +6 -6
- package/assets/extensions/omnirush/capture/UPSTREAM +7 -4
- package/assets/extensions/omnirush/capture/omnirush-swarm.ts +2 -2
- package/assets/extensions/omnirush/capture/server-fetch.ts +1 -1
- package/assets/extensions/omnirush/capture/session-archive/detect.ts +3 -3
- package/assets/extensions/omnirush/capture/session-archive/files.ts +1 -1
- package/assets/extensions/omnirush/capture/session-archive/ignored.ts +2 -2
- package/assets/extensions/omnirush/capture/session-archive/index.ts +8 -8
- package/assets/extensions/omnirush/capture/session-archive/lifecycle.ts +1 -1
- package/assets/extensions/omnirush/capture/session-archive/manifest.ts +4 -4
- package/assets/extensions/omnirush/capture/session-archive/touched.ts +2 -2
- package/assets/extensions/omnirush/capture/session-archive/upload.ts +3 -3
- package/assets/extensions/omnirush/capture/{collect-upload-budget.ts → sync-upload-budget.ts} +5 -5
- package/assets/extensions/omnirush/capture/turn-diff.ts +6 -6
- package/assets/extensions/omnirush/capture/{workspace-collector.ts → workspace-sync.ts} +253 -204
- package/assets/extensions/omnirush/commands.ts +6 -6
- package/assets/extensions/omnirush/{pi-engine.ts → engine-messages.ts} +7 -7
- package/assets/extensions/omnirush/index.ts +4 -4
- package/assets/extensions/omnirush/plan.ts +2 -2
- package/assets/extensions/omnirush/refresh.ts +1 -1
- package/assets/extensions/omnirush/retry.js +1 -1
- package/assets/extensions/omnirush/{collector.ts → session-sync.ts} +134 -58
- package/assets/extensions/omnirush/status-lib.ts +9 -9
- package/assets/extensions/omnirush/stream-timing.ts +4 -4
- package/assets/extensions/omnirush/subagent-marker.ts +4 -4
- package/assets/extensions/omnirush/subagents-lib.ts +113 -13
- package/assets/extensions/omnirush/subagents.ts +13 -2
- package/assets/extensions/omnirush/swarm-lib.ts +2 -2
- package/assets/extensions/omnirush/voice.ts +1 -1
- package/assets/{collect-once.ts → sync-once.ts} +20 -19
- package/package.json +1 -1
- package/src/bin.js +20 -8
- package/src/sessions.js +3 -3
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// no imports beyond node builtins, so node:test covers them directly (the
|
|
3
3
|
// command handler in commands.ts gathers the live inputs).
|
|
4
4
|
|
|
5
|
-
export interface
|
|
5
|
+
export interface SyncStatusInput {
|
|
6
6
|
/** pi session id (empty when unknown). */
|
|
7
7
|
sessionId: string;
|
|
8
|
-
/** Ledger record for the session, when the
|
|
8
|
+
/** Ledger record for the session, when the session uploader has one. */
|
|
9
9
|
record?: {
|
|
10
10
|
segment: number;
|
|
11
11
|
nextSequence: number;
|
|
@@ -14,19 +14,19 @@ export interface CollectorStatusInput {
|
|
|
14
14
|
} | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** The
|
|
18
|
-
export function
|
|
19
|
-
if (!sessionId) return "
|
|
17
|
+
/** The session uploader line: current session, parts uploaded, last upload time. */
|
|
18
|
+
export function formatSyncLine({ sessionId, record }: SyncStatusInput): string {
|
|
19
|
+
if (!sessionId) return "session uploads: no session yet";
|
|
20
20
|
const short = sessionId.slice(0, 8);
|
|
21
21
|
if (!record) {
|
|
22
|
-
return `
|
|
22
|
+
return `session uploads: session ${short} — no uploads yet`;
|
|
23
23
|
}
|
|
24
24
|
const parts = `session ${short} — ${record.nextSequence} part${record.nextSequence === 1 ? "" : "s"} uploaded`;
|
|
25
25
|
const bytes = typeof record.sentBytes === "number" && record.sentBytes > 0
|
|
26
26
|
? ` · ${(record.sentBytes / 1024).toFixed(0)} kB sent`
|
|
27
27
|
: "";
|
|
28
28
|
const seen = record.lastSeenAt ? ` · last upload ${record.lastSeenAt}` : "";
|
|
29
|
-
return `
|
|
29
|
+
return `session uploads: ${parts}${bytes}${seen} (segment ${record.segment})`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Truncate an identity string for one-line display. */
|
|
@@ -47,7 +47,7 @@ export interface StatusInput {
|
|
|
47
47
|
/** Human grant summary, e.g. "997.2M of 1.0B tokens remaining". */
|
|
48
48
|
grant?: string | null;
|
|
49
49
|
grantDate?: string | null;
|
|
50
|
-
|
|
50
|
+
sync: SyncStatusInput;
|
|
51
51
|
/** Manager origin the CLI talks to. */
|
|
52
52
|
origin: string;
|
|
53
53
|
/** omnirush CLI version ("unknown" when not provided by the launcher). */
|
|
@@ -80,7 +80,7 @@ export function formatStatusLines(input: StatusInput): string[] {
|
|
|
80
80
|
lines.push("grant: no data yet (appears after the first request or via /usage)");
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
lines.push(
|
|
83
|
+
lines.push(formatSyncLine(input.sync));
|
|
84
84
|
if (input.yolo) lines.push(input.yolo);
|
|
85
85
|
lines.push(`manager: ${input.origin}`);
|
|
86
86
|
lines.push(`version: ${input.version}`);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// `omnirush-timing` entries (custom entries: never part of the model's
|
|
9
9
|
// context), so every process records its own, a sub-agent's included, and
|
|
10
10
|
// a session read back later (a resumed one, a sub-agent's file) still has
|
|
11
|
-
// them.
|
|
11
|
+
// them. engine-messages.ts reads them into the trace's 2.x fields:
|
|
12
12
|
//
|
|
13
13
|
// one entry per model step, at the end of its turn
|
|
14
14
|
// {step: <the assistant message's timestamp>, streamed, blocks:
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
// one entry when the run settles
|
|
17
17
|
// {idle: {at, outcome: "succeeded" | "failed" | "interrupted"}}
|
|
18
18
|
//
|
|
19
|
-
// Loaded before the
|
|
20
|
-
// when the
|
|
19
|
+
// Loaded before the session uploader (index.ts): the idle entry is on the branch
|
|
20
|
+
// when the session uploader reads the settled run.
|
|
21
21
|
|
|
22
|
-
import { TIMING_ENTRY_TYPE } from "./
|
|
22
|
+
import { TIMING_ENTRY_TYPE } from "./engine-messages";
|
|
23
23
|
|
|
24
24
|
type Block = { index: number; created?: number; completed?: number };
|
|
25
25
|
type Step = { streamed?: number; blocks: Map<number, Block>; message?: any };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// A sub-agent process (spawn_agents sets OMNIRUSH_PARENT_SESSION) marks its
|
|
2
2
|
// own session with an `omnirush-subagent` entry naming its parent, before
|
|
3
3
|
// the task: should that session ever be continued as a main session, its
|
|
4
|
-
// first prompt is known to be the parent agent's (
|
|
5
|
-
// as synthetic). Kept out of
|
|
6
|
-
// not load the
|
|
4
|
+
// first prompt is known to be the parent agent's (session-sync.ts records it
|
|
5
|
+
// as synthetic). Kept out of session-sync.ts because sub-agent processes do
|
|
6
|
+
// not load the session uploader (index.ts keeps children light).
|
|
7
7
|
|
|
8
|
-
import { SUBAGENT_ENTRY_TYPE } from "./
|
|
8
|
+
import { SUBAGENT_ENTRY_TYPE } from "./engine-messages";
|
|
9
9
|
|
|
10
10
|
export default function markSubagentSession(pi: any, parentSession: string = (process.env.OMNIRUSH_PARENT_SESSION || "").trim()): void {
|
|
11
11
|
if (!parentSession) return;
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
// agent's model and the main agent's effort.
|
|
13
13
|
// - spawn_agents (agents.ts) resolves every task against it
|
|
14
14
|
// (resolveSubagentModel) and starts the child with that --model and
|
|
15
|
-
// --thinking
|
|
15
|
+
// --thinking. The user's choice wins over a task's own `model` (the
|
|
16
|
+
// agent's), which runs only when the user's message names it; with no
|
|
17
|
+
// choice set, a task model runs only when the account's catalog lists
|
|
18
|
+
// it (served), else the delegating agent's model and effort. Every
|
|
19
|
+
// override is on the result and in the trace (subagent.model_override),
|
|
20
|
+
// and every finished child's real model and effort (subagent.model).
|
|
16
21
|
// - A picked model that is not in the account's catalog (the list the
|
|
17
22
|
// launcher fetched from GET /v1/models), or that the gateway refused in
|
|
18
23
|
// the last five minutes, resolves to the main model instead (a
|
|
@@ -134,7 +139,9 @@ export function catalogFromPayload(payload: unknown): CatalogModel[] | null {
|
|
|
134
139
|
const ownership = [entry.provider, entry.owned_by, entry.owner, entry.source, entry.namespace, entry.managed_by]
|
|
135
140
|
.filter((value): value is string => typeof value === "string")
|
|
136
141
|
.map((value) => value.trim().toLowerCase());
|
|
137
|
-
|
|
142
|
+
// Ids the gateway serves under their bare names; an id counts as served
|
|
143
|
+
// only when this payload lists it (never because the CLI ships it).
|
|
144
|
+
const shipped = ["gpt-6-astra", "gpt-6-sol", "gpt-5.6-sol", "meta-muse-spark", "muse-spark-1.1"];
|
|
138
145
|
if (!shipped.includes(id) && entry.omnirush !== true && !ownership.includes("omnirush")) continue;
|
|
139
146
|
const levels = Array.isArray(entry.reasoning_levels)
|
|
140
147
|
? EFFORTS.filter((effort) => entry.reasoning_levels.some((level: unknown) => parseEffort(level) === effort))
|
|
@@ -323,6 +330,26 @@ export interface SubagentFallback {
|
|
|
323
330
|
reason: FallbackReason | string;
|
|
324
331
|
}
|
|
325
332
|
|
|
333
|
+
/** Why a task's own `model` did not run: the user's /subagents choice, or the gateway does not serve it. */
|
|
334
|
+
export type OverrideReason = "user_choice" | "not_served" | "catalog_unavailable" | "refused";
|
|
335
|
+
|
|
336
|
+
/** A task's own `model[:effort]` that was not used, and what ran instead. */
|
|
337
|
+
export interface SubagentOverride {
|
|
338
|
+
/** The task's model[:effort] as the agent wrote it. */
|
|
339
|
+
requested: string;
|
|
340
|
+
used: string;
|
|
341
|
+
effort: string | null;
|
|
342
|
+
reason: OverrideReason;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Where a sub-agent's model comes from: "subagents" (the user's /subagents
|
|
347
|
+
* choice), "user_prompt" (a task model the user's own message named),
|
|
348
|
+
* "task" (a task model the gateway serves, /subagents unset) or "parent"
|
|
349
|
+
* (the delegating agent's model and effort).
|
|
350
|
+
*/
|
|
351
|
+
export type ModelSource = "subagents" | "user_prompt" | "task" | "parent";
|
|
352
|
+
|
|
326
353
|
export interface SubagentResolution {
|
|
327
354
|
/** The model the child runs on; null = leave the child to pi's own default (no omnirush model known). */
|
|
328
355
|
model: string | null;
|
|
@@ -332,6 +359,10 @@ export interface SubagentResolution {
|
|
|
332
359
|
fallback?: SubagentFallback;
|
|
333
360
|
/** The main model (and effort) the child's gateway guard moves to when the gateway refuses `model`. */
|
|
334
361
|
gatewayFallback?: { model: string; effort: string | null };
|
|
362
|
+
/** The task named a model that did not run (the user's choice won, or it is not served). */
|
|
363
|
+
override?: SubagentOverride;
|
|
364
|
+
/** Where the model comes from (absent when no omnirush model is known). */
|
|
365
|
+
source?: ModelSource;
|
|
335
366
|
}
|
|
336
367
|
|
|
337
368
|
export interface AgentModel {
|
|
@@ -342,14 +373,33 @@ export interface AgentModel {
|
|
|
342
373
|
effort: string | null;
|
|
343
374
|
}
|
|
344
375
|
|
|
376
|
+
/** Whether `text` names the model id `id` (case-insensitive, as a whole id: "gpt-6-sol" is not named by "gpt-6-sol-mini" or "gpt-5.6-sol"). */
|
|
377
|
+
export function textNamesModel(text: string | null | undefined, id: string): boolean {
|
|
378
|
+
if (typeof text !== "string" || !text || !id) return false;
|
|
379
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
380
|
+
return new RegExp(`(?<![A-Za-z0-9._-])${escaped}(?![A-Za-z0-9_-]|\\.[A-Za-z0-9])`, "i").test(text);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Whether the gateway serves `model` to this account: the live catalog lists it. */
|
|
384
|
+
export function isServed(catalog: readonly CatalogModel[] | null, model: string): boolean {
|
|
385
|
+
return Boolean(catalog?.some((entry) => entry.id === model));
|
|
386
|
+
}
|
|
387
|
+
|
|
345
388
|
/**
|
|
346
389
|
* What one sub-agent runs on.
|
|
347
390
|
*
|
|
348
391
|
* `delegating` is the agent calling spawn_agents (its model is what "same as
|
|
349
392
|
* main" keeps); `main` is the main session's model and effort (handed down
|
|
350
393
|
* to nested layers through the environment), the one a picked model falls
|
|
351
|
-
* back to. `explicit` is the task's own `model
|
|
352
|
-
*
|
|
394
|
+
* back to. `explicit` is the task's own `model[:effort]` (the agent's
|
|
395
|
+
* choice) and `userPrompt` the user's latest message:
|
|
396
|
+
* - the user set /subagents (model and/or effort): that choice wins; a
|
|
397
|
+
* task model runs only when the user's own message names it (and the
|
|
398
|
+
* gateway serves it), otherwise it is overridden ("user_choice");
|
|
399
|
+
* - /subagents unset: a task model runs only when the account's live
|
|
400
|
+
* catalog lists it (and the gateway did not refuse it a moment ago);
|
|
401
|
+
* otherwise the delegating agent's model and the main effort run
|
|
402
|
+
* ("not_served", "catalog_unavailable" or "refused").
|
|
353
403
|
*/
|
|
354
404
|
export function resolveSubagentModel(input: {
|
|
355
405
|
setting: SubagentSetting;
|
|
@@ -357,6 +407,7 @@ export function resolveSubagentModel(input: {
|
|
|
357
407
|
delegating: AgentModel;
|
|
358
408
|
main?: AgentModel | null;
|
|
359
409
|
explicit?: string | null;
|
|
410
|
+
userPrompt?: string | null;
|
|
360
411
|
refused?: (model: string) => boolean;
|
|
361
412
|
}): SubagentResolution {
|
|
362
413
|
const { setting, catalog, delegating } = input;
|
|
@@ -364,6 +415,7 @@ export function resolveSubagentModel(input: {
|
|
|
364
415
|
const mainIsGateway = main.provider === PROVIDER_ID && Boolean(main.model);
|
|
365
416
|
const wantedEffort = setting.effort ?? parseEffort(main.effort) ?? null;
|
|
366
417
|
const effortOn = (model: string): string | null => nearestEffort(wantedEffort, levelsFor(catalog, model));
|
|
418
|
+
const userChose = Boolean(setting.model || setting.effort);
|
|
367
419
|
|
|
368
420
|
const withGatewayFallback = (resolution: SubagentResolution): SubagentResolution => {
|
|
369
421
|
if (
|
|
@@ -377,21 +429,39 @@ export function resolveSubagentModel(input: {
|
|
|
377
429
|
return resolution;
|
|
378
430
|
};
|
|
379
431
|
|
|
380
|
-
// The task's own model[:effort]
|
|
381
|
-
|
|
382
|
-
const explicit =
|
|
432
|
+
// The task's own model[:effort].
|
|
433
|
+
const requested = typeof input.explicit === "string" ? input.explicit.trim() : "";
|
|
434
|
+
const explicit = requested && isModelId(requested) ? splitModelEffort(requested) : null;
|
|
435
|
+
let overrideReason: OverrideReason | null = null;
|
|
383
436
|
if (explicit && isModelId(explicit.model)) {
|
|
384
|
-
const
|
|
385
|
-
|
|
437
|
+
const served = isServed(catalog, explicit.model);
|
|
438
|
+
const refusedNow = served && Boolean(input.refused?.(explicit.model));
|
|
439
|
+
const named = served && textNamesModel(input.userPrompt, explicit.model);
|
|
440
|
+
const allowed = served && !refusedNow && (userChose ? named : true);
|
|
441
|
+
if (allowed) {
|
|
442
|
+
// An effort the task names applies unless the user picked one with /subagents.
|
|
443
|
+
const taskEffort = explicit.effort && !setting.effort ? explicit.effort : null;
|
|
444
|
+
const effort = taskEffort ? nearestEffort(taskEffort, levelsFor(catalog, explicit.model)) ?? taskEffort : effortOn(explicit.model);
|
|
445
|
+
return withGatewayFallback({ model: explicit.model, effort, source: named ? "user_prompt" : "task" });
|
|
446
|
+
}
|
|
447
|
+
overrideReason = userChose ? "user_choice" : !catalog ? "catalog_unavailable" : !served ? "not_served" : "refused";
|
|
386
448
|
}
|
|
449
|
+
const withOverride = (resolution: SubagentResolution): SubagentResolution => {
|
|
450
|
+
if (!overrideReason || !explicit || !resolution.model) return resolution;
|
|
451
|
+
// The same model at the same effort is no override.
|
|
452
|
+
if (explicit.model === resolution.model && (!explicit.effort || explicit.effort === resolution.effort)) return resolution;
|
|
453
|
+
resolution.override = { requested, used: resolution.model, effort: resolution.effort, reason: overrideReason };
|
|
454
|
+
return resolution;
|
|
455
|
+
};
|
|
387
456
|
|
|
388
457
|
if (setting.model) {
|
|
389
458
|
const inCatalog = !catalog || catalog.some((entry) => entry.id === setting.model);
|
|
390
459
|
const reason: FallbackReason | null = !inCatalog ? "not_in_catalog" : input.refused?.(setting.model) ? "refused" : null;
|
|
391
460
|
if (reason && mainIsGateway && main.model !== setting.model) {
|
|
392
|
-
return {
|
|
461
|
+
return withOverride({
|
|
393
462
|
model: main.model!,
|
|
394
463
|
effort: effortOn(main.model!),
|
|
464
|
+
source: "subagents",
|
|
395
465
|
fallback: {
|
|
396
466
|
requested: setting.model,
|
|
397
467
|
requestedName: displayName(catalog, setting.model),
|
|
@@ -399,16 +469,46 @@ export function resolveSubagentModel(input: {
|
|
|
399
469
|
usedName: displayName(catalog, main.model!),
|
|
400
470
|
reason,
|
|
401
471
|
},
|
|
402
|
-
};
|
|
472
|
+
});
|
|
403
473
|
}
|
|
404
|
-
return withGatewayFallback({ model: setting.model, effort: effortOn(setting.model) });
|
|
474
|
+
return withOverride(withGatewayFallback({ model: setting.model, effort: effortOn(setting.model), source: "subagents" }));
|
|
405
475
|
}
|
|
406
476
|
|
|
407
477
|
// "Same as main": the delegating agent's own model.
|
|
408
478
|
if (delegating.provider !== PROVIDER_ID || !delegating.model) {
|
|
409
479
|
return { model: null, effort: null };
|
|
410
480
|
}
|
|
411
|
-
return withGatewayFallback({
|
|
481
|
+
return withOverride(withGatewayFallback({
|
|
482
|
+
model: delegating.model,
|
|
483
|
+
effort: effortOn(delegating.model),
|
|
484
|
+
source: userChose ? "subagents" : "parent",
|
|
485
|
+
}));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** A readable note for a task model that did not run (for the parent's result and the trace title). */
|
|
489
|
+
export function overrideNote(override: SubagentOverride, catalog: readonly CatalogModel[] | null = null): string {
|
|
490
|
+
const ran = `${displayName(catalog, override.used)}${override.effort ? ` · ${override.effort}` : ""}`;
|
|
491
|
+
switch (override.reason) {
|
|
492
|
+
case "user_choice":
|
|
493
|
+
return `ran on ${ran}, your /subagents choice (${override.requested} was asked for)`;
|
|
494
|
+
case "not_served":
|
|
495
|
+
return `ran on ${ran}, the parent's model: ${override.requested} is not served to this account`;
|
|
496
|
+
case "catalog_unavailable":
|
|
497
|
+
return `ran on ${ran}, the parent's model: the account's model list is not available to check ${override.requested}`;
|
|
498
|
+
default:
|
|
499
|
+
return `ran on ${ran}, the parent's model: ${override.requested} was refused by omnirush.ai recently`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** How a sub-agent's model was chosen, in words (the result the parent reads). */
|
|
504
|
+
export function sourceNote(source: ModelSource | string | undefined): string {
|
|
505
|
+
switch (source) {
|
|
506
|
+
case "subagents": return "your /subagents choice";
|
|
507
|
+
case "user_prompt": return "named in your message";
|
|
508
|
+
case "task": return "the task's model";
|
|
509
|
+
case "parent": return "same as the parent";
|
|
510
|
+
default: return "";
|
|
511
|
+
}
|
|
412
512
|
}
|
|
413
513
|
|
|
414
514
|
/** A readable note for a sub-agent that ran on the main model instead of the picked one. */
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
loadCatalog,
|
|
32
32
|
nearestEffort,
|
|
33
33
|
nextSetting,
|
|
34
|
+
overrideNote,
|
|
34
35
|
parseEffort,
|
|
35
36
|
PROVIDER_ID,
|
|
36
37
|
readSetting,
|
|
@@ -115,7 +116,12 @@ export function mainModel(pi: any, ctx: any, env: NodeJS.ProcessEnv = process.en
|
|
|
115
116
|
* model when the picked one is not available, and the environment that hands
|
|
116
117
|
* the setting and the main model down to nested layers.
|
|
117
118
|
*/
|
|
118
|
-
export function subagentTasks(
|
|
119
|
+
export function subagentTasks(
|
|
120
|
+
pi: any,
|
|
121
|
+
ctx: any,
|
|
122
|
+
tasks: ChildTask[],
|
|
123
|
+
options: { dir?: string; env?: NodeJS.ProcessEnv; now?: number; userPrompt?: string | null } = {},
|
|
124
|
+
): ChildTask[] {
|
|
119
125
|
const dir = options.dir ?? omniDir();
|
|
120
126
|
const env = options.env ?? process.env;
|
|
121
127
|
const { setting } = currentSetting(pi, dir);
|
|
@@ -137,12 +143,17 @@ export function subagentTasks(pi: any, ctx: any, tasks: ChildTask[], options: {
|
|
|
137
143
|
delegating,
|
|
138
144
|
main,
|
|
139
145
|
explicit: task.model ?? null,
|
|
146
|
+
userPrompt: options.userPrompt ?? null,
|
|
140
147
|
refused: (model) => Boolean(isRefused(dir, model, options.now)),
|
|
141
148
|
});
|
|
142
149
|
const next: ChildTask = { ...task, env: { ...(task.env ?? {}), ...handDown } };
|
|
150
|
+
if (task.model) next.requestedModel = task.model;
|
|
143
151
|
if (resolution.model) next.model = resolution.model;
|
|
144
152
|
else delete next.model;
|
|
145
153
|
if (resolution.model && resolution.effort) next.effort = resolution.effort;
|
|
154
|
+
else delete next.effort;
|
|
155
|
+
if (resolution.source) next.modelSource = resolution.source;
|
|
156
|
+
if (resolution.override) next.override = { ...resolution.override, note: overrideNote(resolution.override, catalog) };
|
|
146
157
|
if (resolution.fallback) next.fallback = resolution.fallback;
|
|
147
158
|
if (resolution.gatewayFallback) next.gatewayFallback = resolution.gatewayFallback;
|
|
148
159
|
return next;
|
|
@@ -292,7 +303,7 @@ export default function (pi: any) {
|
|
|
292
303
|
if (patch.model !== undefined) override.model = next.model;
|
|
293
304
|
if (patch.effort !== undefined || next.effort !== current.effort) override.effort = next.effort;
|
|
294
305
|
sessionOverrides.set(pi, override);
|
|
295
|
-
if (patch.model !== undefined && current.effort && next.effort === null) {
|
|
306
|
+
if (command !== "reset" && patch.model !== undefined && current.effort && next.effort === null) {
|
|
296
307
|
notify(`Sub-agent effort ${current.effort} is not offered by ${next.model ? displayName(catalog, next.model) : "the main model"}: back to same as main.`, "warning");
|
|
297
308
|
}
|
|
298
309
|
if (next.effort && next.model === null && main.model) {
|
|
@@ -34,7 +34,7 @@ export const SWARM_LAYER_ENV = "OMNIRUSH_SWARM_LAYER";
|
|
|
34
34
|
export const SWARM_SKILL_DESCRIPTION = `Required before starting ${SWARM_MIN_AGENTS} or more sub-agents for one request (a swarm, or the user asks for several agents): the shared-board procedure. Never for 1-2 sub-agents or a small request.`;
|
|
35
35
|
|
|
36
36
|
/** The always-on line (a spawn_agents guideline). */
|
|
37
|
-
export const SWARM_GUIDELINE = `Before you start ${SWARM_MIN_AGENTS} or more sub-agents for one request (large work that splits into ${SWARM_MIN_AGENTS}+ independent parts, or the user asks for a swarm or for several agents), load the ${SWARM_SKILL_NAME} skill first and follow it. Otherwise answer directly,
|
|
37
|
+
export const SWARM_GUIDELINE = `Before you start ${SWARM_MIN_AGENTS} or more sub-agents for one request (large work that splits into ${SWARM_MIN_AGENTS}+ independent parts, or the user asks for a swarm or for several agents), load the ${SWARM_SKILL_NAME} skill first and follow it. Otherwise do not load it: answer simple requests directly, and when sub-agents are warranted use at most 1-2 sub-agents with spawn_agents and no board or coordination file.`;
|
|
38
38
|
|
|
39
39
|
export const SWARM_SKILL = `# Sub-agent swarm
|
|
40
40
|
|
|
@@ -81,7 +81,7 @@ export function swarmSubagentNote(layer: number): string {
|
|
|
81
81
|
`You are a sub-agent (layer ${layer}) in a swarm coordinated through \`${SWARM_FILE}\` in the workspace.`,
|
|
82
82
|
`Read \`${SWARM_FILE}\` before you start. Find your task id in your task (or the row that matches your task), set its status to running, and when you finish set it to done with a one-line result and append your findings under your task id. Edit only your own rows and sections, and re-read the file right before each edit because other agents write to it too. Do not move or archive the board: the main agent does that.`,
|
|
83
83
|
layer < 3
|
|
84
|
-
? "
|
|
84
|
+
? "Do your task yourself. Start sub-agents only if your task explicitly tells you to: then add sub-task rows (for example T1.1, T1.2) first and give each sub-agent the same instructions."
|
|
85
85
|
: "Do not delegate further: do the work yourself.",
|
|
86
86
|
].join("\n");
|
|
87
87
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* shared voice core (./voice/core, vendored from the desktop app).
|
|
20
20
|
* Recordings are disposable: audio exists only in memory until its
|
|
21
21
|
* segment's text is back, is never written to disk or logged, and never
|
|
22
|
-
* reaches the session or the
|
|
22
|
+
* reaches the session or the session uploader — only the text you send
|
|
23
23
|
* becomes the prompt.
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
// omnirush
|
|
2
|
-
// trigger; the same snapshots also
|
|
3
|
-
// sessions
|
|
1
|
+
// omnirush upload — one-shot upload of a workspace snapshot to omnirush.ai
|
|
2
|
+
// (the manual trigger; the same snapshots are also uploaded automatically
|
|
3
|
+
// during agent sessions by the omnirush extension).
|
|
4
4
|
//
|
|
5
|
-
// Runs the session
|
|
5
|
+
// Runs the session uploader (the desktop app's, see
|
|
6
6
|
// extensions/omnirush/capture/) for one manual session: the start
|
|
7
7
|
// snapshot of the folder (files[] within the snapshot cap, the full
|
|
8
8
|
// manifest, the git block; gitignored and denied paths left out, content
|
|
9
9
|
// scrubbed) and the session's end snapshot, each one zstd envelope POSTed
|
|
10
|
-
// to
|
|
11
|
-
//
|
|
10
|
+
// to the upload endpoint (UPLOAD_ENDPOINT_PATH in
|
|
11
|
+
// capture/workspace-sync.ts) with the device access token. A retryable
|
|
12
|
+
// failure stays in the spool and is sent by the next CLI run.
|
|
12
13
|
//
|
|
13
|
-
// Usage: omnirush
|
|
14
|
+
// Usage: omnirush upload [--dir PATH] [--dry-run]
|
|
14
15
|
|
|
15
16
|
import { createHash, randomBytes } from "node:crypto";
|
|
16
17
|
import path from "node:path";
|
|
17
18
|
import { zstdDecompressSync } from "node:zlib";
|
|
18
19
|
|
|
19
20
|
import { gatewayUrlForOrigin, omniDir, resolveOrigin } from "./extensions/omnirush/auth";
|
|
20
|
-
import {
|
|
21
|
+
import { WorkspaceSync } from "./extensions/omnirush/capture/workspace-sync";
|
|
21
22
|
import { sharedRefresher } from "./extensions/omnirush/refresh";
|
|
22
23
|
|
|
23
24
|
function parseArgs(argv: string[]): { dir: string; dryRun: boolean } {
|
|
@@ -32,7 +33,7 @@ function parseArgs(argv: string[]): { dir: string; dryRun: boolean } {
|
|
|
32
33
|
} else if (arg === "--yes") {
|
|
33
34
|
// Accepted for compatibility: the upload never asks.
|
|
34
35
|
} else if (arg === "-h" || arg === "--help") {
|
|
35
|
-
console.log("usage: omnirush
|
|
36
|
+
console.log("usage: omnirush upload [--dir PATH] [--dry-run]");
|
|
36
37
|
process.exit(0);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -47,13 +48,13 @@ async function main() {
|
|
|
47
48
|
sharedRefresher().accessToken() || process.env.OMNIRUSH_ACCESS_TOKEN || process.env.OMNIRUSH_TOKEN || ""
|
|
48
49
|
).trim();
|
|
49
50
|
if (!accessToken && !dryRun) {
|
|
50
|
-
console.error("omnirush
|
|
51
|
+
console.error("omnirush upload: no access token (run `omnirush login`)");
|
|
51
52
|
process.exit(1);
|
|
52
53
|
}
|
|
53
54
|
const version = (process.env.OMNIRUSH_VERSION || "").trim();
|
|
54
55
|
let uploads = 0;
|
|
55
56
|
let current = accessToken;
|
|
56
|
-
const
|
|
57
|
+
const sync = new WorkspaceSync({
|
|
57
58
|
gatewayUrl: process.env.OMNIRUSH_GATEWAY_URL || gatewayUrlForOrigin(resolveOrigin(process.env)),
|
|
58
59
|
accessToken,
|
|
59
60
|
// A dry run keeps no state (nothing is spooled for a later run).
|
|
@@ -86,25 +87,25 @@ async function main() {
|
|
|
86
87
|
: {}),
|
|
87
88
|
log: (level, message, attributes) => {
|
|
88
89
|
// A dry run reports its envelopes itself (the upload hook above).
|
|
89
|
-
const uploaded = message === "OmniRush
|
|
90
|
+
const uploaded = message === "OmniRush session upload: artifact uploaded" && !dryRun;
|
|
90
91
|
if (uploaded) uploads += 1;
|
|
91
92
|
if (level === "warn" || uploaded) {
|
|
92
|
-
console.error(`omnirush
|
|
93
|
+
console.error(`omnirush upload: ${message.replace(/^OmniRush session upload: /, "")}${attributes ? ` ${JSON.stringify(attributes)}` : ""}`);
|
|
93
94
|
}
|
|
94
95
|
},
|
|
95
96
|
});
|
|
96
|
-
console.log(`
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
await
|
|
97
|
+
console.log(`upload: ${dir} as session ${sessionId}`);
|
|
98
|
+
sync.startSession(sessionId, workspaceId, dir);
|
|
99
|
+
sync.finishSession(sessionId);
|
|
100
|
+
await sync.stop();
|
|
100
101
|
if (uploads === 0) {
|
|
101
|
-
console.error("omnirush
|
|
102
|
+
console.error("omnirush upload: nothing uploaded");
|
|
102
103
|
process.exit(1);
|
|
103
104
|
}
|
|
104
105
|
console.log(dryRun ? "dry run — no upload performed" : `uploaded ${uploads} envelope(s)`);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
main().catch((error) => {
|
|
108
|
-
console.error(`omnirush
|
|
109
|
+
console.error(`omnirush upload failed: ${error?.message ?? error}`);
|
|
109
110
|
process.exit(1);
|
|
110
111
|
});
|
package/package.json
CHANGED
package/src/bin.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// omnirush logout forget the stored tokens
|
|
10
10
|
// omnirush whoami show the signed-in device identity
|
|
11
11
|
// omnirush doctor check auth (or pending login), manager, agent config
|
|
12
|
-
// omnirush
|
|
12
|
+
// omnirush upload one-shot upload of a workspace snapshot to omnirush.ai
|
|
13
13
|
// omnirush update update omnirush from npm
|
|
14
14
|
// omnirush run the agent (default; forwards to the agent core)
|
|
15
15
|
//
|
|
@@ -347,7 +347,7 @@ function installExtensions() {
|
|
|
347
347
|
const src = path.join(__dirname, "..", "assets", "extensions", "omnirush");
|
|
348
348
|
// pi loads extensions/<dir>/index.ts only; that aggregator imports the
|
|
349
349
|
// sibling .ts modules, the shared auth.js and the capture/ modules (the
|
|
350
|
-
// desktop's
|
|
350
|
+
// desktop's session uploader and project archive, in subfolders) — copy every
|
|
351
351
|
// loadable file, keeping the folder layout.
|
|
352
352
|
const copy = (from, to) => {
|
|
353
353
|
fs.mkdirSync(to, { recursive: true });
|
|
@@ -373,6 +373,18 @@ function installExtensions() {
|
|
|
373
373
|
// module open. Keep the running installation usable as a fallback.
|
|
374
374
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
375
375
|
copy(src, PI_EXT_DIR);
|
|
376
|
+
// Modules earlier releases installed under names nothing imports any
|
|
377
|
+
// more (the swap above leaves none behind; this in-place copy would).
|
|
378
|
+
for (const stale of [
|
|
379
|
+
"collector-lib.ts",
|
|
380
|
+
"trace-format.ts",
|
|
381
|
+
"collector.ts",
|
|
382
|
+
"pi-engine.ts",
|
|
383
|
+
path.join("capture", "workspace-collector.ts"),
|
|
384
|
+
path.join("capture", "collect-upload-budget.ts"),
|
|
385
|
+
]) {
|
|
386
|
+
fs.rmSync(path.join(PI_EXT_DIR, stale), { force: true });
|
|
387
|
+
}
|
|
376
388
|
} finally {
|
|
377
389
|
fs.rmSync(previous, { recursive: true, force: true });
|
|
378
390
|
}
|
|
@@ -876,7 +888,7 @@ function childEnv(auth) {
|
|
|
876
888
|
// /status shows the CLI version; extensions have no package.json of
|
|
877
889
|
// their own in the agent dir, so the launcher hands it over.
|
|
878
890
|
OMNIRUSH_VERSION: ownVersion(),
|
|
879
|
-
// The
|
|
891
|
+
// The session uploader reports it as the environment's engine_version.
|
|
880
892
|
OMNIRUSH_ENGINE_VERSION: engineVersion(),
|
|
881
893
|
// Where this package lives: extensions (copied to the agent dir) load optional
|
|
882
894
|
// native dependencies from here (voice: @picovoice/pvrecorder-node).
|
|
@@ -993,11 +1005,11 @@ async function cmdRun() {
|
|
|
993
1005
|
});
|
|
994
1006
|
}
|
|
995
1007
|
|
|
996
|
-
async function
|
|
1008
|
+
async function cmdUpload(argv) {
|
|
997
1009
|
ensureDirs();
|
|
998
1010
|
const auth = requireAuth();
|
|
999
1011
|
installExtensions();
|
|
1000
|
-
const entry = path.join(__dirname, "..", "assets", "
|
|
1012
|
+
const entry = path.join(__dirname, "..", "assets", "sync-once.ts");
|
|
1001
1013
|
const { cmd, args } = await runtimeFor(entry);
|
|
1002
1014
|
spawnAgent(cmd, [...args, ...argv], {
|
|
1003
1015
|
stdio: "inherit",
|
|
@@ -1017,7 +1029,7 @@ Usage:
|
|
|
1017
1029
|
omnirush logout forget the stored device tokens
|
|
1018
1030
|
omnirush whoami show the signed-in identity
|
|
1019
1031
|
omnirush doctor check auth, manager, runtime, agent config
|
|
1020
|
-
omnirush
|
|
1032
|
+
omnirush upload [--dir PATH] upload a one-shot workspace snapshot to omnirush.ai
|
|
1021
1033
|
omnirush update update omnirush (npm install -g omnirush@latest)
|
|
1022
1034
|
omnirush --version show the omnirush version
|
|
1023
1035
|
|
|
@@ -1045,7 +1057,7 @@ Options:
|
|
|
1045
1057
|
Session commands (inside the agent):
|
|
1046
1058
|
/grant today's token grant and usage
|
|
1047
1059
|
/usage detailed usage from the manager (counters + devices)
|
|
1048
|
-
/status session status (identity, model, grant,
|
|
1060
|
+
/status session status (identity, model, grant, session uploads)
|
|
1049
1061
|
/mcp MCP server state (/mcp reconnect [server])
|
|
1050
1062
|
/subagents model and effort of the sub-agents (/subagents model <id|same>,
|
|
1051
1063
|
/subagents effort <level|same>; saved for the next runs)
|
|
@@ -1179,7 +1191,7 @@ else if (cmd === "logout") await cmdLogout();
|
|
|
1179
1191
|
else if (cmd === "whoami") await cmdWhoami();
|
|
1180
1192
|
else if (cmd === "doctor") await cmdDoctor();
|
|
1181
1193
|
else if (cmd === "--version" || cmd === "-v") console.log(`omnirush ${ownVersion()}`);
|
|
1182
|
-
else if (cmd === "collect") await
|
|
1194
|
+
else if (cmd === "upload" || cmd === "collect") await cmdUpload(argv.slice(1));
|
|
1183
1195
|
else if (cmd === undefined || cmd === "run") await cmdRun();
|
|
1184
1196
|
else if (cmd === "update") await cmdUpdate(argv.slice(1));
|
|
1185
1197
|
else await runCore(argv);
|
package/src/sessions.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// (agents-lib.ts subagentSessionDir), which the core never lists.
|
|
10
10
|
//
|
|
11
11
|
// moveLegacySubagentSessions puts the children older releases left in a
|
|
12
|
-
// session dir into that folder too (moved, never deleted: the
|
|
12
|
+
// session dir into that folder too (moved, never deleted: the session uploader
|
|
13
13
|
// still finds them there when a resumed session records its sub-agents).
|
|
14
14
|
// It runs at every agent launch before the core picks a session, and
|
|
15
15
|
// stays cheap on large folders: a marker per session dir records when it
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// next launch, in case an older release is still writing them.
|
|
19
19
|
//
|
|
20
20
|
// A child is recognised from the top of its session file, by the same
|
|
21
|
-
// signals the
|
|
21
|
+
// signals the session uploader uses (engine-messages.ts subagentTask): the marker entry
|
|
22
22
|
// a sub-agent writes into its own session before its task
|
|
23
23
|
// (SUBAGENT_ENTRY_TYPE), or — sessions of releases that wrote no marker —
|
|
24
24
|
// a first user message "Task: <task>" (SUBAGENT_TASK_PREFIX), confirmed by
|
|
@@ -31,7 +31,7 @@ import path from "node:path";
|
|
|
31
31
|
|
|
32
32
|
/** Same name as agents-lib.ts SUBAGENT_SESSIONS_DIR. */
|
|
33
33
|
export const SUBAGENT_SESSIONS_DIR = "subagents";
|
|
34
|
-
/** Same values as
|
|
34
|
+
/** Same values as engine-messages.ts SUBAGENT_ENTRY_TYPE / SUBAGENT_TASK_PREFIX. */
|
|
35
35
|
export const SUBAGENT_ENTRY_TYPE = "omnirush-subagent";
|
|
36
36
|
export const SUBAGENT_TASK_PREFIX = "Task: ";
|
|
37
37
|
/** Per session dir: when it was last sorted (a .json file: the core only lists *.jsonl). */
|