omnirush 0.10.1 → 0.10.2

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 (34) hide show
  1. package/assets/extensions/omnirush/agents-lib.ts +18 -6
  2. package/assets/extensions/omnirush/agents.ts +7 -4
  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} +54 -54
  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/swarm-lib.ts +2 -2
  30. package/assets/extensions/omnirush/voice.ts +1 -1
  31. package/assets/{collect-once.ts → sync-once.ts} +20 -19
  32. package/package.json +1 -1
  33. package/src/bin.js +20 -8
  34. package/src/sessions.js +3 -3
@@ -5,7 +5,7 @@
5
5
  * /usage full usage from the manager (grant date, token counters,
6
6
  * devices)
7
7
  * /status one-screen session summary (identity, model/effort, grant,
8
- * collector, origin, version)
8
+ * session uploader, origin, version)
9
9
  * /verify prove the served model is genuine — full implementation
10
10
  * lands with the gateway's model-attestation endpoint (M2)
11
11
  * plus the footer status line that keeps grant/model info visible while
@@ -17,10 +17,10 @@
17
17
  * which is exactly how /grant and /verify broke the first time.
18
18
  */
19
19
 
20
- import { readSessionLedger, SESSION_LEDGER_FILE } from "./capture/workspace-collector";
20
+ import { readSessionLedger, SESSION_LEDGER_FILE } from "./capture/workspace-sync";
21
21
  import { deviceMe, omniDir, resolveOrigin, sessionEndedMessage } from "./auth";
22
22
  import { sharedRefresher } from "./refresh";
23
- import { formatCollectorLine, formatStatusLines } from "./status-lib";
23
+ import { formatSyncLine, formatStatusLines } from "./status-lib";
24
24
  import { formatModeStatus } from "./yolo-lib";
25
25
  import {
26
26
  formatDeviceLines,
@@ -129,7 +129,7 @@ export default function (pi: ExtensionAPI) {
129
129
  });
130
130
 
131
131
  pi.registerCommand("status", {
132
- description: "Show the Omnirush session status (identity, model, grant, collector)",
132
+ description: "Show the Omnirush session status (identity, model, grant, session uploads)",
133
133
  handler: async (_args: string, ctx: any) => {
134
134
  const { me } = await fetchMe();
135
135
  const usage = gatewayUsage();
@@ -140,7 +140,7 @@ export default function (pi: ExtensionAPI) {
140
140
  const ledger = await readSessionLedger(`${omniDir()}/${SESSION_LEDGER_FILE}`);
141
141
  record = ledger.sessions[sessionId] ?? null;
142
142
  } catch {
143
- /* collector state is best effort */
143
+ /* session upload state is best effort */
144
144
  }
145
145
  const model = ctx?.model;
146
146
  const statusLines = formatStatusLines({
@@ -151,7 +151,7 @@ export default function (pi: ExtensionAPI) {
151
151
  effort: usage?.["x-omnirush-effort"] ?? null,
152
152
  grant: grant?.text ?? null,
153
153
  grantDate: grant?.date ?? null,
154
- collector: { sessionId, record },
154
+ sync: { sessionId, record },
155
155
  origin: resolveOrigin(process.env),
156
156
  version: CLI_VERSION,
157
157
  yolo: formatModeStatus(),
@@ -1,6 +1,6 @@
1
1
  // The CLI's session, in the desktop app's engine message format.
2
2
  //
3
- // The desktop collector records the conversation as the engine (opencode)
3
+ // The desktop app records the conversation as the engine (opencode)
4
4
  // keeps it: one message per user prompt and per model step,
5
5
  //
6
6
  // {info: {id, sessionID, role, time: {created, completed}, modelID,
@@ -72,7 +72,7 @@
72
72
  // outcome) comes from stream-timing.ts's `omnirush-timing` entries.
73
73
  //
74
74
  // Also here, pure and shared with the tests: the helpers the desktop's turn
75
- // observer (apps/server/src/collector-observer.ts) applies to the engine's
75
+ // observer (apps/server/src) applies to the engine's
76
76
  // messages when a turn settles: the turn's model, the message budget of a
77
77
  // flush and its split into `turn.messages` parts.
78
78
 
@@ -1010,9 +1010,9 @@ export function parseSessionFile(text: string): { header: PiEntry | null; entrie
1010
1010
  return { header, entries };
1011
1011
  }
1012
1012
 
1013
- // --- the turn observer's helpers (collector-observer.ts) -------------------------
1013
+ // --- the turn observer's helpers (the desktop turn observer) -------------------------
1014
1014
 
1015
- /** Latest model selection of a turn, as the collector's session.model event records it. */
1015
+ /** Latest model selection of a turn, as the session uploader's session.model event records it. */
1016
1016
  export type TurnModel = { provider_id: string | null; model_id: string | null; variant: string | null; agent: string | null };
1017
1017
 
1018
1018
  function optionalText(value: unknown): string | null {
@@ -1023,7 +1023,7 @@ function optionalText(value: unknown): string | null {
1023
1023
  * The model the turn ran on, read from the turn's first assistant message
1024
1024
  * (providerID / modelID / mode or agent); the user message that opened the
1025
1025
  * turn supplies the variant and agent when the assistant record lacks them.
1026
- * (collector-observer.ts `turnModelFromMessages`.)
1026
+ * (the desktop turn observer `turnModelFromMessages`.)
1027
1027
  */
1028
1028
  export function turnModelFromMessages(messages: unknown): TurnModel | null {
1029
1029
  if (!Array.isArray(messages)) return null;
@@ -1071,7 +1071,7 @@ export type TurnHistory = {
1071
1071
  * The part of a session's messages a settled turn records: every message
1072
1072
  * after the checkpoint message (all of them without one, or when the
1073
1073
  * checkpoint is not on the branch), newest first up to `budget` bytes, and
1074
- * once the budget is spent only the user's own prompts. (collector-observer.ts
1074
+ * once the budget is spent only the user's own prompts. (the desktop turn observer
1075
1075
  * `readEngineHistory`, over messages already in memory.)
1076
1076
  */
1077
1077
  export function turnHistory(messages: readonly EngineMessage[], checkpoint: string | undefined, budget = MAX_BACKLOG_MESSAGE_BYTES): TurnHistory {
@@ -1112,7 +1112,7 @@ export type MessagePart = { messages: unknown[]; bytes: number };
1112
1112
  * A settled turn's messages (oldest first) cut into the parts its trace
1113
1113
  * flushes carry, oldest part first: the newest messages up to `maxBytes` make
1114
1114
  * the last part, and each earlier part takes the next older ones. One part,
1115
- * empty, when there are no messages. (collector-observer.ts `messageParts`.)
1115
+ * empty, when there are no messages. (the desktop turn observer `messageParts`.)
1116
1116
  */
1117
1117
  export function messageParts(messages: readonly unknown[], sizes: readonly number[], maxBytes = MAX_TURN_MESSAGE_BYTES): MessagePart[] {
1118
1118
  const parts: MessagePart[] = [];
@@ -8,7 +8,7 @@
8
8
  //
9
9
  // A sub-agent (a headless spawn_agents child: OMNIRUSH_PARENT_SESSION is
10
10
  // set) does not load what only the interactive top agent uses — the slash
11
- // commands (commands), the session capture (collector: the parent records
11
+ // commands (commands), the session capture (session uploader: the parent records
12
12
  // its children) and voice input (TUI only) — so each of the many child
13
13
  // processes a fan-out starts stays smaller. They are imported on demand,
14
14
  // in the same order as before, so the top agent registers exactly what it
@@ -39,11 +39,11 @@ export default async function (pi: ExtensionAPI) {
39
39
  if (!subagent) (await import("./commands")).default(pi as any);
40
40
  recorder(pi as any);
41
41
  // Every process times its own runs (the 2.x trace fields); before the
42
- // collector, so a settled run's idle entry is written before it is read.
42
+ // session uploader, so a settled run's idle entry is written before it is read.
43
43
  recordStreamTiming(pi as any);
44
44
  sota(pi as any);
45
- if (!subagent) (await import("./collector")).default(pi as any);
46
- // Children still mark their own session as a sub-agent's (the collector
45
+ if (!subagent) (await import("./session-sync")).default(pi as any);
46
+ // Children still mark their own session as a sub-agent's (the session uploader
47
47
  // does that for the top agent's sessions; children do not load it).
48
48
  else (await import("./subagent-marker")).default(pi as any);
49
49
  mcp(pi as any);
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // The model maintains an ordered task list (items with pending /
4
4
  // in-progress / done statuses) while it works. Every update:
5
- // - persists to <cwd>/__agent__/plan.json (the workspace collector
5
+ // - persists to <cwd>/__agent__/plan.json (the workspace uploader
6
6
  // ships it with the workspace snapshots),
7
7
  // - appends an `omnirush.plan` custom entry to the session JSONL (the
8
8
  // tool call itself, with the whole list, reaches the trace as a tool
@@ -41,7 +41,7 @@ import {
41
41
  const PLAN_TOOL_NAME = "plan";
42
42
  const PLAN_ENTRY_TYPE = "omnirush.plan";
43
43
  const PLAN_STATUS_KEY = "omnirush-plan";
44
- // The plan file's folder in the workspace (the CLI's earlier collector
44
+ // The plan file's folder in the workspace (the CLI's earlier uploader
45
45
  // kept its journal there too).
46
46
  const AGENT_DIR = "__agent__";
47
47
 
@@ -1,5 +1,5 @@
1
1
  // Process-wide single-flight refresher shared by the sota guard and the
2
- // collector (both run inside the pi process). Lazily created from the
2
+ // session uploader (both run inside the pi process). Lazily created from the
3
3
  // environment; refreshes rotate both tokens back to the state dir (0600).
4
4
 
5
5
  import { createRefresher, omniDir, resolveOrigin } from "./auth";
@@ -1,5 +1,5 @@
1
1
  // Omnirush retry layer — shared by every Omnirush API call path (model
2
- // stream via the sota tap, /collect uploads, /device and /device/me).
2
+ // stream via the sota tap, session uploads, /device and /device/me).
3
3
  //
4
4
  // Goal: users ride out a backend deploy or a rate-limit window without
5
5
  // seeing a single raw error. Retryable = 429 / 5xx / network failures.
@@ -2,10 +2,10 @@
2
2
  // the same format, as the desktop app.
3
3
  //
4
4
  // The capture itself is the desktop's, ported unchanged but for a few
5
- // marked seams (capture/: workspace-collector.ts, turn-diff.ts and the
5
+ // marked seams (capture/: workspace-sync.ts, turn-diff.ts and the
6
6
  // project archive in session-archive/, from omnirush-gui apps/server/src).
7
7
  // This file is the CLI's counterpart of the desktop's turn observer
8
- // (collector-observer.ts) and server hooks: it tells the collector and the
8
+ // (the desktop turn observer) and server hooks: it tells the session uploader and the
9
9
  // project archive what the agent does, from pi's events.
10
10
  //
11
11
  // a prompt goes out engine.request trace event (prompt, model,
@@ -13,7 +13,7 @@
13
13
  // events), the "prompt" change snapshot, the
14
14
  // project archive's session start (its base)
15
15
  // a run settles the conversation since the last flush as
16
- // engine messages (pi-engine.ts): session.model,
16
+ // engine messages (engine-messages.ts): session.model,
17
17
  // turn.messages parts when it is long, each
18
18
  // sub-agent's new messages as session.child, the
19
19
  // "turn_completed" snapshot with its turn.diff, and
@@ -41,7 +41,7 @@
41
41
  // it. A prompt an extension sends (pi's input event, source "extension")
42
42
  // is synthetic too.
43
43
  //
44
- // Silent no-op without device credentials: collection only runs for
44
+ // Silent no-op without device credentials: session uploads only run for
45
45
  // signed-in omnirush identities.
46
46
 
47
47
  import { createHash } from "node:crypto";
@@ -53,7 +53,7 @@ import path from "node:path";
53
53
  import { createInterface } from "node:readline";
54
54
 
55
55
  import { gatewayUrlForOrigin, omniDir, resolveOrigin } from "./auth";
56
- import { WorkspaceCollector } from "./capture/workspace-collector";
56
+ import { WorkspaceSync } from "./capture/workspace-sync";
57
57
  import { SessionArchiver } from "./capture/session-archive/index";
58
58
  import { ProjectArchiveLifecycle, projectArchiveEnabled } from "./capture/session-archive/lifecycle";
59
59
  import {
@@ -68,14 +68,14 @@ import {
68
68
  userText,
69
69
  type EngineMessage,
70
70
  type PiEntry,
71
- } from "./pi-engine";
71
+ } from "./engine-messages";
72
72
  import { sharedRefresher } from "./refresh";
73
73
  import { AGENTS_HOOK, SUBAGENT_SESSIONS_DIR, type AgentsHook } from "./agents-lib";
74
74
  import { BGSHELL_HOOK, type BgShellHook } from "./bgshell-lib";
75
75
 
76
76
  /** info.agent of the root session's messages (a sub-agent's is its role). */
77
77
  export const ROOT_AGENT = "omnirush-cli";
78
- /** Sub-agent layers captured below the root session (the desktop's MAX_COLLECTOR_CHILD_SESSION_DEPTH). */
78
+ /** Sub-agent layers captured below the root session (the desktop's MAX_SYNC_CHILD_SESSION_DEPTH). */
79
79
  const MAX_CHILD_DEPTH = 3;
80
80
  /** Sub-agent sessions followed per session (the desktop's MAX_CHILD_SESSIONS). */
81
81
  const MAX_CHILDREN = 200;
@@ -105,7 +105,7 @@ function piAgentDir(): string {
105
105
  return path.join(stateDir ? path.resolve(stateDir) : path.join(os.homedir(), ".omnirush"), "agent");
106
106
  }
107
107
 
108
- /** pi session ids are UUIDs; the collector refuses anything outside its id alphabet. */
108
+ /** pi session ids are UUIDs; the session uploader refuses anything outside its id alphabet. */
109
109
  function sanitizeSessionId(raw: string): string {
110
110
  const id = String(raw || "");
111
111
  return /^[A-Za-z0-9._:-]{8,128}$/.test(id) ? id : "";
@@ -128,7 +128,7 @@ function cliVersion(): string {
128
128
  }
129
129
  }
130
130
 
131
- /** The previous collector's per-session change journals (`journal-<session>.ndjson`); the ported collector keeps none. */
131
+ /** The previous session uploader's per-session change journals (`journal-<session>.ndjson`); the ported session uploader keeps none. */
132
132
  function removeLegacyJournals(stateDir: string): void {
133
133
  try {
134
134
  for (const name of readdirSync(stateDir)) {
@@ -307,20 +307,20 @@ export async function parseSessionFileStream(file: string): Promise<{ header: Pi
307
307
  return { header, entries };
308
308
  }
309
309
 
310
- type Collector = Pick<
311
- WorkspaceCollector,
310
+ type Sync = Pick<
311
+ WorkspaceSync,
312
312
  "recordTrace" | "recordChildSession" | "childCheckpoints" | "childSessionIds"
313
313
  >;
314
314
 
315
315
  /**
316
316
  * Records the sub-agent tree below a settled session: one session.child event
317
317
  * per sub-agent carrying only its messages since its checkpoint, recursively
318
- * for the sub-agents it started (collector-observer.ts
318
+ * for the sub-agents it started (the desktop turn observer
319
319
  * `captureChildSessions`). `beforeRecord` hears the size of each event's
320
320
  * messages before it is recorded.
321
321
  */
322
322
  export async function captureChildSessions(input: {
323
- collector: Collector;
323
+ sync: Sync;
324
324
  rootSessionId: string;
325
325
  parentSessionId: string;
326
326
  parentEntries: readonly PiEntry[];
@@ -354,13 +354,13 @@ export async function captureChildSessions(input: {
354
354
  const messages = engineMessagesFromEntries(entries, { sessionId: child.id, agent, cwd });
355
355
  const history = turnHistory(messages, input.checkpoints[child.id], MAX_TURN_MESSAGE_BYTES);
356
356
  if (history.omitted > 0) {
357
- input.collector.recordTrace(input.rootSessionId, "session.messages_omitted", { child_session_id: child.id, count: history.omitted });
357
+ input.sync.recordTrace(input.rootSessionId, "session.messages_omitted", { child_session_id: child.id, count: history.omitted });
358
358
  }
359
359
  const fallbackKey = `${input.rootSessionId}:${child.id}`;
360
360
  if (child.modelFallback && !recordedFallbacks.has(fallbackKey)) {
361
361
  // As the desktop: the sub-agent ran on the main model, and why.
362
362
  recordedFallbacks.add(fallbackKey);
363
- input.collector.recordTrace(input.rootSessionId, "subagent.model_fallback", {
363
+ input.sync.recordTrace(input.rootSessionId, "subagent.model_fallback", {
364
364
  child_session_id: child.id,
365
365
  requested: child.modelFallback.requested,
366
366
  used: child.modelFallback.used,
@@ -371,7 +371,7 @@ export async function captureChildSessions(input: {
371
371
  }
372
372
  if (history.delta.length > 0 || !input.known.has(child.id)) {
373
373
  await input.beforeRecord(history.sizes.reduce((total, bytes) => total + bytes, 0));
374
- input.collector.recordChildSession(input.rootSessionId, {
374
+ input.sync.recordChildSession(input.rootSessionId, {
375
375
  childSessionId: child.id,
376
376
  parentSessionId: input.parentSessionId,
377
377
  depth: input.depth,
@@ -391,7 +391,7 @@ export default function (pi: any) {
391
391
  const parentSession = (process.env.OMNIRUSH_PARENT_SESSION || "").trim();
392
392
  if (parentSession) {
393
393
  // Its session says so (subagent-marker.ts; index.ts loads it for
394
- // sub-agent processes, which do not load the collector).
394
+ // sub-agent processes, which do not load the session uploader).
395
395
  markSubagentSession(pi, parentSession);
396
396
  return;
397
397
  }
@@ -411,7 +411,7 @@ export default function (pi: any) {
411
411
  if (level !== "warn" && !verbose) return;
412
412
  try {
413
413
  const detail = attributes && Object.keys(attributes).length > 0 ? ` ${JSON.stringify(attributes)}` : "";
414
- process.stderr.write(`omnirush collector: ${message}${detail}\n`);
414
+ process.stderr.write(`omnirush session upload: ${message}${detail}\n`);
415
415
  } catch {
416
416
  /* stderr may be closed late in shutdown */
417
417
  }
@@ -431,7 +431,7 @@ export default function (pi: any) {
431
431
 
432
432
  // Uploads carry the CURRENT on-disk token, not the one this process
433
433
  // last saw: another terminal or a sub-agent may have rotated the pair.
434
- // Only requests that already carry a bearer (collect, archive API) are
434
+ // Only requests that already carry a bearer (session upload, archive API) are
435
435
  // touched — presigned S3 part PUTs pass through unchanged.
436
436
  const withCurrentBearer = (input: string, init?: RequestInit): Promise<Response> => {
437
437
  const headers = new Headers(init?.headers ?? {});
@@ -450,7 +450,7 @@ export default function (pi: any) {
450
450
  return globalThis.fetch(input, init);
451
451
  };
452
452
 
453
- const collector = new WorkspaceCollector({
453
+ const sync = new WorkspaceSync({
454
454
  gatewayUrl,
455
455
  accessToken,
456
456
  refreshAccessToken,
@@ -484,12 +484,12 @@ export default function (pi: any) {
484
484
  });
485
485
  const archive = new ProjectArchiveLifecycle({
486
486
  archiver,
487
- enabled: projectArchiveEnabled(process.env) && collector.enabled,
487
+ enabled: projectArchiveEnabled(process.env) && sync.enabled,
488
488
  log,
489
489
  baseIdleMs: ARCHIVE_BASE_IDLE_MS,
490
490
  baseMaxDeferMs: ARCHIVE_BASE_MAX_DEFER_MS,
491
491
  });
492
- if (!collector.enabled) {
492
+ if (!sync.enabled) {
493
493
  // Signed out: clear what a previous run left queued (no network).
494
494
  archive.start();
495
495
  return;
@@ -502,21 +502,21 @@ export default function (pi: any) {
502
502
  id: string;
503
503
  root: string;
504
504
  ctx: any;
505
- /** A prompt went out in this process: the collector follows the session. */
505
+ /** A prompt went out in this process: the session uploader follows the session. */
506
506
  started: boolean;
507
507
  /** A prompt went out and its run has not settled. */
508
508
  running: boolean;
509
- /** The id of the last engine message recorded (the collector's checkpoint). */
509
+ /** The id of the last engine message recorded (the session uploader's checkpoint). */
510
510
  lastMessageId: string | undefined;
511
511
  toolStarts: Map<string, number>;
512
512
  seenFiles: Map<string, string>;
513
513
  /** Prompts sent in this process with the model and effort they went with, until their user entry is found. */
514
514
  pendingPrompts: Array<{ at: number; model: { providerID: string; modelID: string } | null; variant: string | null }>;
515
- /** User entry id -> what its prompt was sent with (pi-engine promptSettings). */
515
+ /** User entry id -> what its prompt was sent with (engine-messages promptSettings). */
516
516
  promptSettings: Map<string, { model: { providerID: string; modelID: string } | null; variant: string | null }>;
517
517
  /** Prompts an extension sent (pi's input event, source "extension"), until their user entry is found. */
518
518
  extensionInputs: Array<{ at: number; text: string }>;
519
- /** User entries the user did not submit -> their parts' metadata (pi-engine injectedPrompts). */
519
+ /** User entries the user did not submit -> their parts' metadata (engine-messages injectedPrompts). */
520
520
  injectedPrompts: Map<string, Record<string, unknown>>;
521
521
  /** Whether the session is a sub-agent's (its first prompt the parent agent's task), once looked up. */
522
522
  origin: Promise<void> | null;
@@ -581,7 +581,7 @@ export default function (pi: any) {
581
581
  };
582
582
 
583
583
  /**
584
- * The session `ctx` is on. Its collection starts with its first prompt in
584
+ * The session `ctx` is on. Its upload starts with its first prompt in
585
585
  * this process, as the desktop starts it on a prompt dispatch: opening a
586
586
  * session and leaving without a prompt uploads nothing.
587
587
  */
@@ -609,7 +609,7 @@ export default function (pi: any) {
609
609
  session.ctx = ctx;
610
610
  // Idempotent: a session already started in this process keeps its segment.
611
611
  if (prompted) {
612
- collector.startSession(id, workspaceIdFor(session.root), session.root);
612
+ sync.startSession(id, workspaceIdFor(session.root), session.root);
613
613
  session.started = true;
614
614
  }
615
615
  return session;
@@ -624,20 +624,20 @@ export default function (pi: any) {
624
624
  const { id } = state;
625
625
  try {
626
626
  await captureChildSessions({
627
- collector,
627
+ sync,
628
628
  rootSessionId: id,
629
629
  parentSessionId: id,
630
630
  parentEntries: state.ctx?.sessionManager?.getBranch?.() ?? [],
631
631
  depth: 1,
632
632
  sessionDirs: sessionDirs(state.ctx),
633
- checkpoints: await collector.childCheckpoints(id),
634
- known: new Set(await collector.childSessionIds(id)),
633
+ checkpoints: await sync.childCheckpoints(id),
634
+ known: new Set(await sync.childSessionIds(id)),
635
635
  visited: new Set([id]),
636
636
  seenFiles: state.seenFiles,
637
637
  beforeRecord: room,
638
638
  });
639
639
  } catch (error) {
640
- collector.recordTrace(id, "session.children_failed", { error: error instanceof Error ? error.message : "unknown" });
640
+ sync.recordTrace(id, "session.children_failed", { error: error instanceof Error ? error.message : "unknown" });
641
641
  }
642
642
  };
643
643
 
@@ -653,9 +653,9 @@ export default function (pi: any) {
653
653
  let pending = 0;
654
654
  return async (bytes: number): Promise<void> => {
655
655
  if (pending > 0 && pending + bytes > MAX_TURN_MESSAGE_BYTES) {
656
- collector.flushTrace(id);
656
+ sync.flushTrace(id);
657
657
  pending = 0;
658
- await collector.drained(id);
658
+ await sync.drained(id);
659
659
  }
660
660
  pending += bytes;
661
661
  };
@@ -673,37 +673,37 @@ export default function (pi: any) {
673
673
  let parts: Array<{ messages: unknown[]; bytes: number }> = [{ messages: [], bytes: 0 }];
674
674
  try {
675
675
  messages = messagesOf(state);
676
- const checkpoint = state.lastMessageId ?? (await collector.sessionCheckpoint(id)).lastMessageId;
676
+ const checkpoint = state.lastMessageId ?? (await sync.sessionCheckpoint(id)).lastMessageId;
677
677
  const history = turnHistory(messages, checkpoint);
678
678
  const lastId = messages.at(-1)?.info.id;
679
679
  if (typeof lastId === "string") {
680
680
  state.lastMessageId = lastId;
681
- void collector.setSessionCheckpoint(id, lastId);
681
+ void sync.setSessionCheckpoint(id, lastId);
682
682
  }
683
- if (history.omitted > 0) collector.recordTrace(id, "session.messages_omitted", { count: history.omitted });
683
+ if (history.omitted > 0) sync.recordTrace(id, "session.messages_omitted", { count: history.omitted });
684
684
  const model = turnModelFromMessages(history.delta);
685
- if (model) collector.recordSessionModel(id, model);
685
+ if (model) sync.recordSessionModel(id, model);
686
686
  // More messages than one flush holds: the older ones go ahead, a flush per part.
687
687
  parts = messageParts(history.delta, history.sizes);
688
688
  for (const [index, part] of parts.slice(0, -1).entries()) {
689
- collector.recordTrace(id, "turn.messages", { messages: part.messages, part: index + 1, parts: parts.length });
690
- collector.flushTrace(id);
689
+ sync.recordTrace(id, "turn.messages", { messages: part.messages, part: index + 1, parts: parts.length });
690
+ sync.flushTrace(id);
691
691
  // One part waits in memory at a time (a resumed session's backlog is up to 32 MiB).
692
- await collector.drained(id);
692
+ await sync.drained(id);
693
693
  }
694
694
  } catch (error) {
695
- collector.recordTrace(id, "session.messages_failed", { error: error instanceof Error ? error.message : "unknown" });
695
+ sync.recordTrace(id, "session.messages_failed", { error: error instanceof Error ? error.message : "unknown" });
696
696
  }
697
697
  const newest = parts.at(-1)!;
698
698
  // Sub-agent messages flush on their own before they would crowd the turn's out.
699
699
  const room = childRoom(id);
700
700
  await captureChildren(state, room);
701
701
  await room(newest.bytes);
702
- collector.recordTrace(id, "session.idle", { status });
702
+ sync.recordTrace(id, "session.idle", { status });
703
703
  // The turn snapshot runs first so the artifacts it discovers are part of
704
704
  // the trace flushed right behind it.
705
- collector.captureSnapshot(id, "turn_completed");
706
- collector.flushTrace(id, { messages: newest.messages });
705
+ sync.captureSnapshot(id, "turn_completed");
706
+ sync.flushTrace(id, { messages: newest.messages });
707
707
  archive.turnCompleted(id, messages);
708
708
  };
709
709
 
@@ -735,14 +735,14 @@ export default function (pi: any) {
735
735
  const state = session;
736
736
  if (!state?.started) return;
737
737
  for (const child of interrupted) {
738
- collector.recordTrace(state.id, "session.child_interrupted", { child_session_id: child.sessionId, agent: child.role });
738
+ sync.recordTrace(state.id, "session.child_interrupted", { child_session_id: child.sessionId, agent: child.role });
739
739
  }
740
740
  const lastId = messagesOf(state).at(-1)?.info.id;
741
741
  if (typeof lastId !== "string" || lastId === state.lastMessageId) {
742
742
  // No new message of its own: what its sub-agents did since the last settle.
743
743
  settling = settling.then(async () => {
744
744
  await captureChildren(state, childRoom(state.id));
745
- collector.flushTrace(state.id);
745
+ sync.flushTrace(state.id);
746
746
  }).catch(() => undefined);
747
747
  await settling;
748
748
  return;
@@ -755,7 +755,7 @@ export default function (pi: any) {
755
755
  let stopping: Promise<void> | null = null;
756
756
  const shutdown = (budgetMs: number): Promise<void> => (stopping ??= stop(budgetMs));
757
757
  const stop = async (budgetMs: number): Promise<void> => {
758
- // A ref'd timer: the collector's and the archive's own timers are unref'd,
758
+ // A ref'd timer: the session uploader's and the archive's own timers are unref'd,
759
759
  // and pi awaits this handler without holding the process open, so without
760
760
  // it a process with nothing else to do exits in the middle of an upload.
761
761
  let budget: ReturnType<typeof setTimeout> | undefined;
@@ -770,12 +770,12 @@ export default function (pi: any) {
770
770
  if (state) {
771
771
  // The settled turn's own snapshot (its turn.diff) and trace go out
772
772
  // before the session ends: a finished session skips queued captures.
773
- await collector.idle(state.id);
774
- collector.finishSession(state.id);
773
+ await sync.idle(state.id);
774
+ sync.finishSession(state.id);
775
775
  }
776
776
  // The base and turn deltas upload now; what is left waits in the queue.
777
777
  await archive.settled();
778
- await collector.stop();
778
+ await sync.stop();
779
779
  })(),
780
780
  deadline,
781
781
  ]);
@@ -837,7 +837,7 @@ export default function (pi: any) {
837
837
  // The prompt as the desktop records its engine request: text, attached
838
838
  // files (inline bytes replaced by a marker), model, variant, agent and
839
839
  // the system prompt it ran with.
840
- collector.recordTrace(state.id, "engine.request", {
840
+ sync.recordTrace(state.id, "engine.request", {
841
841
  method: "POST",
842
842
  path: `/session/${state.id}/prompt`,
843
843
  body: {
@@ -861,7 +861,7 @@ export default function (pi: any) {
861
861
  images.forEach((image: any, index: number) => {
862
862
  if (typeof image?.data !== "string") return;
863
863
  const bytes = Buffer.from(image.data, "base64");
864
- collector.recordAttachment(state.id, {
864
+ sync.recordAttachment(state.id, {
865
865
  name: `image-${index + 1}`,
866
866
  mime: typeof image.mimeType === "string" ? image.mimeType : "application/octet-stream",
867
867
  bytes: bytes.length,
@@ -869,7 +869,7 @@ export default function (pi: any) {
869
869
  text: null,
870
870
  });
871
871
  });
872
- collector.captureSnapshot(state.id, "prompt");
872
+ sync.captureSnapshot(state.id, "prompt");
873
873
  archive.sessionStarted({
874
874
  sessionId: state.id,
875
875
  root: state.root,
@@ -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;
@@ -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
  }