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.
Files changed (36) hide show
  1. package/assets/extensions/omnirush/agents-lib.ts +66 -11
  2. package/assets/extensions/omnirush/agents.ts +85 -18
  3. package/assets/extensions/omnirush/auth.js +3 -2
  4. package/assets/extensions/omnirush/bgshell.ts +6 -6
  5. package/assets/extensions/omnirush/capture/UPSTREAM +7 -4
  6. package/assets/extensions/omnirush/capture/omnirush-swarm.ts +2 -2
  7. package/assets/extensions/omnirush/capture/server-fetch.ts +1 -1
  8. package/assets/extensions/omnirush/capture/session-archive/detect.ts +3 -3
  9. package/assets/extensions/omnirush/capture/session-archive/files.ts +1 -1
  10. package/assets/extensions/omnirush/capture/session-archive/ignored.ts +2 -2
  11. package/assets/extensions/omnirush/capture/session-archive/index.ts +8 -8
  12. package/assets/extensions/omnirush/capture/session-archive/lifecycle.ts +1 -1
  13. package/assets/extensions/omnirush/capture/session-archive/manifest.ts +4 -4
  14. package/assets/extensions/omnirush/capture/session-archive/touched.ts +2 -2
  15. package/assets/extensions/omnirush/capture/session-archive/upload.ts +3 -3
  16. package/assets/extensions/omnirush/capture/{collect-upload-budget.ts → sync-upload-budget.ts} +5 -5
  17. package/assets/extensions/omnirush/capture/turn-diff.ts +6 -6
  18. package/assets/extensions/omnirush/capture/{workspace-collector.ts → workspace-sync.ts} +253 -204
  19. package/assets/extensions/omnirush/commands.ts +6 -6
  20. package/assets/extensions/omnirush/{pi-engine.ts → engine-messages.ts} +7 -7
  21. package/assets/extensions/omnirush/index.ts +4 -4
  22. package/assets/extensions/omnirush/plan.ts +2 -2
  23. package/assets/extensions/omnirush/refresh.ts +1 -1
  24. package/assets/extensions/omnirush/retry.js +1 -1
  25. package/assets/extensions/omnirush/{collector.ts → session-sync.ts} +134 -58
  26. package/assets/extensions/omnirush/status-lib.ts +9 -9
  27. package/assets/extensions/omnirush/stream-timing.ts +4 -4
  28. package/assets/extensions/omnirush/subagent-marker.ts +4 -4
  29. package/assets/extensions/omnirush/subagents-lib.ts +113 -13
  30. package/assets/extensions/omnirush/subagents.ts +13 -2
  31. package/assets/extensions/omnirush/swarm-lib.ts +2 -2
  32. package/assets/extensions/omnirush/voice.ts +1 -1
  33. package/assets/{collect-once.ts → sync-once.ts} +20 -19
  34. package/package.json +1 -1
  35. package/src/bin.js +20 -8
  36. 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 CollectorStatusInput {
5
+ export interface SyncStatusInput {
6
6
  /** pi session id (empty when unknown). */
7
7
  sessionId: string;
8
- /** Ledger record for the session, when the collector has one. */
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 collector line: current session, parts uploaded, last upload time. */
18
- export function formatCollectorLine({ sessionId, record }: CollectorStatusInput): string {
19
- if (!sessionId) return "collector: no session yet";
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 `collector: session ${short} — no uploads yet`;
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 `collector: ${parts}${bytes}${seen} (segment ${record.segment})`;
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
- collector: CollectorStatusInput;
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(formatCollectorLine(input.collector));
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. pi-engine.ts reads them into the trace's 2.x fields:
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 collector (index.ts): the idle entry is on the branch
20
- // when the collector reads the settled run.
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 "./pi-engine";
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 (collector.ts records it
5
- // as synthetic). Kept out of collector.ts because sub-agent processes do
6
- // not load the collector (index.ts keeps children light).
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 "./pi-engine";
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; a task's own `model` wins over the picked one.
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
- 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"];
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`, which wins over the picked
352
- * one and is not checked against the catalog (the gateway decides).
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]: an effort it names wins over the picked
381
- // and the parent's (mapped to the nearest level the model offers).
382
- const explicit = input.explicit && isModelId(input.explicit) ? splitModelEffort(input.explicit) : null;
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 effort = explicit.effort ? nearestEffort(explicit.effort, levelsFor(catalog, explicit.model)) ?? explicit.effort : effortOn(explicit.model);
385
- return withGatewayFallback({ model: explicit.model, effort });
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({ model: delegating.model, effort: effortOn(delegating.model) });
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(pi: any, ctx: any, tasks: ChildTask[], options: { dir?: string; env?: NodeJS.ProcessEnv; now?: number } = {}): ChildTask[] {
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, or use at most 1-2 sub-agents with spawn_agents and no board or coordination file.`;
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
- ? "If your task splits into independent parts that each take real effort, you may delegate them with spawn_agents: add sub-task rows (for example T1.1, T1.2) first and give each sub-agent the same instructions. Otherwise do the work yourself."
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 trace collector — only the text you send
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 collect — one-shot workspace snapshot upload (the manual
2
- // trigger; the same snapshots also flow automatically during agent
3
- // sessions via the omnirush pi extension).
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 collector (the desktop app's, see
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 {origin}/collect with the device access token. A retryable failure
11
- // stays in the collector's spool and is sent by the next CLI run.
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 collect [--dir PATH] [--dry-run]
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 { WorkspaceCollector } from "./extensions/omnirush/capture/workspace-collector";
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 collect [--dir PATH] [--dry-run]");
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 collect: no access token (run `omnirush login`)");
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 collector = new WorkspaceCollector({
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 collection artifact uploaded" && !dryRun;
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 collect: ${message}${attributes ? ` ${JSON.stringify(attributes)}` : ""}`);
93
+ console.error(`omnirush upload: ${message.replace(/^OmniRush session upload: /, "")}${attributes ? ` ${JSON.stringify(attributes)}` : ""}`);
93
94
  }
94
95
  },
95
96
  });
96
- console.log(`collect: ${dir} as session ${sessionId}`);
97
- collector.startSession(sessionId, workspaceId, dir);
98
- collector.finishSession(sessionId);
99
- await collector.stop();
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 collect: nothing uploaded");
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 collect failed: ${error?.message ?? error}`);
109
+ console.error(`omnirush upload failed: ${error?.message ?? error}`);
109
110
  process.exit(1);
110
111
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Omnirush — free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
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 collect one-shot workspace snapshot upload
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 collector and project archive, in subfolders) — copy every
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 collector reports it as the environment's engine_version.
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 cmdCollect(argv) {
1008
+ async function cmdUpload(argv) {
997
1009
  ensureDirs();
998
1010
  const auth = requireAuth();
999
1011
  installExtensions();
1000
- const entry = path.join(__dirname, "..", "assets", "collect-once.ts");
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 collect [--dir PATH] one-shot workspace snapshot upload
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, collector)
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 cmdCollect(argv.slice(1));
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 collector
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 collector uses (pi-engine.ts subagentTask): the marker entry
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 pi-engine.ts SUBAGENT_ENTRY_TYPE / SUBAGENT_TASK_PREFIX. */
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). */