@nledford/omp-coord 0.2.2 → 0.2.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.
@@ -7,7 +7,7 @@
7
7
  "name": "omp-coord",
8
8
  "source": "./",
9
9
  "description": "Shared subagent caps, build and test queues, claims, pause, a decision log and messages across omp sessions working in one project on one machine.",
10
- "version": "0.2.2",
10
+ "version": "0.2.3",
11
11
  "author": { "name": "Nathaniel Ledford" },
12
12
  "homepage": "https://github.com/nledford/omp-coord",
13
13
  "repository": "https://github.com/nledford/omp-coord",
package/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ Add each user-visible change under Unreleased as it lands, in one of: Added, Cha
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.3] - 2026-10-27
10
+
11
+ ### Added
12
+
13
+ - `tagAgentCommands` (off by default): each queued job records the agent whose command queued it, and the queue lines in `/coord` and `omp-coord status` read `session A/WaveImport`; `status --json` has `agent` on each job and the `job_queued` event carries it. With it on, the extension starts every agent bash command with `export OMP_COORD_AGENT=<agent id>;`, which `omp-coord run` reads; leave it off if you use omp's `bash.patterns` rules or another extension that rewrites bash commands. A board made by an older omp-coord gains the new column when this version opens it; while any session of the scope still runs an older omp-coord, its saves drop the recorded agents (display only).
14
+
15
+ ### Changed
16
+
17
+ - A queue line shows the job's key (for example `rebase-2204 ci`) instead of its command when the job has one; the command still shows when it doesn't, and `status --json` keeps both.
18
+
9
19
  ## [0.2.2] - 2026-10-27
10
20
 
11
21
  ### Added
@@ -53,7 +63,8 @@ Add each user-visible change under Unreleased as it lands, in one of: Added, Cha
53
63
  - An SQLite event log per scope, with retention.
54
64
  - Packaging as an omp plugin, with a marketplace catalog; queued commands run on omp's own Bun.
55
65
 
56
- [Unreleased]: https://github.com/nledford/omp-coord/compare/v0.2.2...HEAD
66
+ [Unreleased]: https://github.com/nledford/omp-coord/compare/v0.2.3...HEAD
67
+ [0.2.3]: https://github.com/nledford/omp-coord/compare/v0.2.2...v0.2.3
57
68
  [0.2.2]: https://github.com/nledford/omp-coord/compare/v0.2.1...v0.2.2
58
69
  [0.2.1]: https://github.com/nledford/omp-coord/compare/v0.2.0...v0.2.1
59
70
  [0.2.0]: https://github.com/nledford/omp-coord/compare/v0.1.0...v0.2.0
package/docs/commands.md CHANGED
@@ -61,7 +61,7 @@ Every time is milliseconds since the epoch (`Date.now()`); a session is its lett
61
61
  | `line[]` | Sessions waiting for a slot: `pool`, `session`, `position` (from 1), `since`, `heldUntil` (`null` unless a freed slot is held for it). |
62
62
  | `agents[]` | One per agent holding a slot, by session letter then `since`: `id` (`null` for a spawn with no key and no first run yet), `type`, `parent` (`Main` or an agent id), `session`, `pool` (`null`: outside the caps), `state` (`starting` or `running`), `since`. |
63
63
  | `claims[]` | `kind` (`branch`, `worktree`, `pr`), `target`, `session`, `agent`, `note`, `since`. |
64
- | `queues[]` | `name`, `order`, `slots` (now), `configuredSlots`, `running[]` and `waiting[]` (in the order they will start), each job: `id`, `session`, `owner` (the owning session's id, or `null` outside omp), `lane`, `priority`, `key` (or `null`), `command`, `state`, `queuedAt`, `startedAt` (`null` while waiting), `position` (from 1 while waiting, `null` while running). |
64
+ | `queues[]` | `name`, `order`, `slots` (now), `configuredSlots`, `running[]` and `waiting[]` (in the order they will start), each job: `id`, `session`, `owner` (the owning session's id, or `null` outside omp), `lane`, `priority`, `key` (or `null`), `command`, `agent` (omp's id of the agent whose command queued it, `"Main"` for the main agent; `null` when `tagAgentCommands` is off, for a job started outside an agent's bash call, and while any session of the scope runs an omp-coord older than 0.2.3, whose saves drop it), `state`, `queuedAt`, `startedAt` (`null` while waiting), `position` (from 1 while waiting, `null` while running). |
65
65
  | `notes[]` | The latest three decisions, oldest first: `id`, `at`, `session` (the letter when it was written, kept after that session ends; `null` outside omp), `author`, `text`. |
66
66
 
67
67
  ```json
@@ -102,4 +102,5 @@ Exit codes: `0` success, the command's own for `run`, `75` a job not run, `124`
102
102
  | `OMP_COORD_SESSION` | the extension | The session that owns jobs started from its agents' commands. |
103
103
  | `OMP_COORD_PORTS` | the extension | The session's port block, e.g. `18700-18799`, when `resources.ports` is set. |
104
104
  | `OMP_COORD_SCRATCH` | the extension | The session's scratch folder, when `resources.scratch` is set. |
105
+ | `OMP_COORD_AGENT` | the extension, with `tagAgentCommands` | The agent whose command this is (`Main` for the main agent); `omp-coord run` records it on the job. |
105
106
  | `OMP_COORD_JOB`, `OMP_COORD_QUEUE` | `omp-coord run` | The running job's id and queue, for nested runs. |
@@ -21,6 +21,7 @@
21
21
  | `queues.<name>.onDuplicate` | `"refuse"` | `"refuse"` or `"supersede"` a waiting job with the same key. |
22
22
  | `queues.<name>.timeoutMinutes` | none | Stop a job's command after this long: SIGTERM to its process group, then SIGKILL to whatever of it is left 10 s later; it exits 124. `--timeout` overrides it per job. |
23
23
  | `queues.<name>.loadSlots` | `[]` | `[{ "above": 150, "slots": 1 }]`: fewer slots under load; `0` holds the queue. |
24
+ | `tagAgentCommands` | `false` | Start each agent bash command with `export OMP_COORD_AGENT=<agent id>;`, so each queued job records the agent that queued it (queue lines read `session A/<agent>`). Leave it off when you use omp's `bash.patterns` allow or deny rules (a rewritten command no longer matches them) or another extension that rewrites bash commands (the last rewrite wins). |
24
25
  | `resources.ports` | none | `{ "from", "to", "perSession", "perAgent" (10) }`: each session gets a block of `perSession` ports, each agent a block of `perAgent` from its session's. |
25
26
  | `resources.scratch` | none | Root of the scratch folders: `<scratch>/<letter>` per session, `<scratch>/<letter>/<agent id>` per agent. Relative to the config file. |
26
27
  | `history.retentionDays` | `30` | Events and decision-log notes older than this are deleted. |
package/docs/event-log.md CHANGED
@@ -14,7 +14,7 @@ These tables are rebuilt as sessions come and go; their layout is not part of th
14
14
  | `slots` | Agent slots held: `session_id`, `agent_id`, `parent_id`, `agent_type`, `pool` (null outside the caps), `state` (`starting` or `running`), `since`. |
15
15
  | `tickets` | The line: `session_id`, `pool`, `since`, `hold_until` (set while a slot is held for it), `notified`. |
16
16
  | `messages` | Messages not yet delivered. |
17
- | `jobs` | Queued and running build/test jobs: `id`, `queue`, `owner` (session id, or null outside omp), `lane`, `priority`, `key`, `command`, `pid`, `state` (`waiting`, `running`, `superseded`), `since`, `started_at`. |
17
+ | `jobs` | Queued and running build/test jobs: `id`, `queue`, `owner` (session id, or null outside omp), `lane`, `priority`, `key`, `command`, `agent` (null when not recorded), `pid`, `state` (`waiting`, `running`, `superseded`), `since`, `started_at`. |
18
18
  | `claims` | `kind` (`branch`, `worktree`, `pr`), `target`, `session_id`, `agent_id`, `note`, `since`. |
19
19
  | `pause` | One row while the scope is paused: `by_session` (null from a terminal), `reason`, `since`. |
20
20
  | `agent_ports` | Port blocks agents took: `session_id`, `agent_id`, `from_port`, `to_port`. |
@@ -25,7 +25,7 @@ These tables are rebuilt as sessions come and go; their layout is not part of th
25
25
  `events` is an append-only log: `at` (ms since epoch), `type`, `session_id` (null for jobs from outside omp), `label`, and `data` (JSON). Types:
26
26
 
27
27
  - Sessions and agents: `session_joined`, `session_left`, `spawn_approved`, `spawn_refused` (with `position`, `used`, `cap`, or `nested` and `reason`), `run_started` (with `revived`), `run_ended` (with `ranMs`), `start_expired`, `hold_granted`, `hold_used` and `hold_expired` (with `waitedMs` since the first refusal), `message_sent` (recipients and length, never the text).
28
- - Jobs: `job_queued` (with `queue`, `lane`, `priority`, `key`), `job_refused` (with `reason`), `job_started` (with `waitedMs`), `job_ended` (with `exitCode`, `ranMs`, and `timedOut` when the time limit stopped it), `job_withdrawn` (left the line before starting), `job_superseded`, `job_lost` (its runner died), `job_reprioritized`.
28
+ - Jobs: `job_queued` (with `queue`, `lane`, `priority`, `key`, and `agent` when recorded), `job_refused` (with `reason`), `job_started` (with `waitedMs`), `job_ended` (with `exitCode`, `ranMs`, and `timedOut` when the time limit stopped it), `job_withdrawn` (left the line before starting), `job_superseded`, `job_lost` (its runner died), `job_reprioritized`.
29
29
  - Control: `paused` (with `reason`), `resumed` (with `pausedMs`), `claimed`, `released`, `claim_refused` (with `holder`), `note_added` (note number and length).
30
30
 
31
31
  `notes` is the decision log: `id`, `at`, `label`, `author`, `text`.
package/docs/queues.md CHANGED
@@ -49,4 +49,6 @@ A command that puts its work in the background (`cargo test &`) holds the slot o
49
49
 
50
50
  When an agent's bash command matches a `queue` rule under `commands`, the extension rewrites it to run `omp-coord run <queue> --owner <session> -- sh -c '<command>'` with omp's own binary as Bun (`BUN_BE_BUN=1`), so it needs no separate Bun install. The waiting happens inside the bash call, which counts against the bash tool's timeout: agents should run long jobs as background (`async`) bash calls. The rewritten command runs in `sh`, so shell variables the agent set earlier without exporting them are not visible to it.
51
51
 
52
+ With `tagAgentCommands` on, the extension also starts every agent bash command with `export OMP_COORD_AGENT=<agent id>;`, and `omp-coord run` records that agent on the job: queue lines read `session A/WaveImport` instead of only `session A`. It is off by default because the rewritten command no longer matches omp's `bash.patterns` rules (they must match the whole command) and replaces any other extension's rewrite of the same call.
53
+
52
54
  A command matching a `refuse` rule is refused with the rule's reason.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nledford/omp-coord",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "files": [
5
5
  "src",
6
6
  "docs",
@@ -249,6 +249,8 @@ async function run(args: string[]): Promise<number> {
249
249
  id,
250
250
  queue,
251
251
  owner: values.owner || process.env.OMP_COORD_SESSION || null,
252
+ // set per command by the extension's bash hook
253
+ ...(process.env.OMP_COORD_AGENT ? { agent: process.env.OMP_COORD_AGENT } : {}),
252
254
  ...(values.lane === undefined ? {} : { lane: values.lane }),
253
255
  priority,
254
256
  ...(values.key === undefined ? {} : { key: values.key }),
@@ -16,7 +16,7 @@ import { join, resolve } from "node:path";
16
16
  import { Coordinator } from "../../app/coordinator.ts";
17
17
  import type { Listener } from "../../app/ports.ts";
18
18
  import { type Config, ConfigError, findConfigFile, loadConfig } from "../../config.ts";
19
- import { decideCommand } from "../../domain/commands.ts";
19
+ import { decideCommand, withAgent } from "../../domain/commands.ts";
20
20
  import type { Claim } from "../../domain/model.ts";
21
21
  import { osLoad } from "../os/load.ts";
22
22
  import { BoardStore, stateDir } from "../sqlite/store.ts";
@@ -190,7 +190,12 @@ export default function ompCoord(pi: ExtensionAPI): void {
190
190
  config: rt.config.source,
191
191
  });
192
192
  if (decision.kind === "refuse") return { block: true, reason: `omp-coord: ${decision.reason}` };
193
- if (decision.kind === "queue") return { input: { ...event.input, command: decision.command } };
193
+ const run = decision.kind === "queue" ? decision.command : command;
194
+ // Every agent of a session shares the omp process's environment, so the agent's id can reach `omp-coord run` (and
195
+ // the queue lines) only through the command itself; opt-in (`tagAgentCommands`), since the rewrite defeats omp's
196
+ // bash.patterns rules and other extensions' rewrites. Off, the hook changes only queued commands, as before.
197
+ if (rt.config.tagAgentCommands) return { input: { ...event.input, command: withAgent(ctx.agent.id, run) } };
198
+ if (decision.kind === "queue") return { input: { ...event.input, command: run } };
194
199
  return undefined;
195
200
  });
196
201
 
@@ -77,6 +77,7 @@ CREATE TABLE jobs (
77
77
  priority INTEGER NOT NULL,
78
78
  key TEXT,
79
79
  command TEXT NOT NULL,
80
+ agent TEXT,
80
81
  socket TEXT NOT NULL,
81
82
  pid INTEGER NOT NULL,
82
83
  state TEXT NOT NULL CHECK (state IN ('waiting', 'running', 'superseded')),
@@ -229,7 +230,10 @@ export class BoardStore implements BoardRepository {
229
230
 
230
231
  #migrate(): void {
231
232
  const version = this.#db.query<{ user_version: number }, []>("PRAGMA user_version").get()?.user_version ?? 0;
232
- if (version === SCHEMA_VERSION) return;
233
+ if (version === SCHEMA_VERSION) {
234
+ this.#addColumns();
235
+ return;
236
+ }
233
237
  if (version !== 0) {
234
238
  throw new Error(
235
239
  `the board database has schema ${version}, but this omp-coord reads schema ${SCHEMA_VERSION}; update omp-coord`,
@@ -239,6 +243,20 @@ export class BoardStore implements BoardRepository {
239
243
  this.#db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
240
244
  }
241
245
 
246
+ /**
247
+ * Nullable columns added after schema 1 was released, without a version bump: a board made by an older omp-coord
248
+ * gets them here, and an older omp-coord still running (an omp session not yet restarted) keeps reading and
249
+ * writing the board, ignoring them (its saves write null there, which readers show as unknown).
250
+ */
251
+ #addColumns(): void {
252
+ const has = (table: string, column: string) =>
253
+ this.#db
254
+ .query<{ name: string }, []>(`PRAGMA table_info(${table})`)
255
+ .all()
256
+ .some(c => c.name === column);
257
+ if (!has("jobs", "agent")) this.#db.exec("ALTER TABLE jobs ADD COLUMN agent TEXT");
258
+ }
259
+
242
260
  #maybePruneEvents(now: number): void {
243
261
  const last = Number(
244
262
  this.#db.query<{ value: string }, []>("SELECT value FROM meta WHERE key = 'events_pruned_at'").get()?.value ?? 0,
@@ -337,6 +355,7 @@ export class BoardStore implements BoardRepository {
337
355
  priority: number;
338
356
  key: string | null;
339
357
  command: string;
358
+ agent: string | null;
340
359
  socket: string;
341
360
  pid: number;
342
361
  state: "waiting" | "running" | "superseded";
@@ -354,6 +373,7 @@ export class BoardStore implements BoardRepository {
354
373
  priority: r.priority,
355
374
  key: r.key ?? undefined,
356
375
  command: r.command,
376
+ agent: r.agent ?? undefined,
357
377
  socket: r.socket,
358
378
  pid: r.pid,
359
379
  state: r.state,
@@ -455,7 +475,7 @@ export class BoardStore implements BoardRepository {
455
475
  const sent = db.query("INSERT INTO sent (session_id, at) VALUES (?, ?)");
456
476
  for (const x of board.sent) sent.run(x.sessionId, x.at);
457
477
  const job = db.query(
458
- "INSERT INTO jobs (id, queue, owner, lane, priority, key, command, socket, pid, state, since, started_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
478
+ "INSERT INTO jobs (id, queue, owner, lane, priority, key, command, agent, socket, pid, state, since, started_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
459
479
  );
460
480
  for (const j of board.jobs) {
461
481
  job.run(
@@ -466,6 +486,7 @@ export class BoardStore implements BoardRepository {
466
486
  j.priority,
467
487
  j.key ?? null,
468
488
  j.command,
489
+ j.agent ?? null,
469
490
  j.socket,
470
491
  j.pid,
471
492
  j.state,
@@ -5,7 +5,7 @@ import { capOf, used } from "../../domain/agents.ts";
5
5
  import { pauseReason } from "../../domain/control.ts";
6
6
  import type { DayStats, InterruptedSession } from "../../domain/history.ts";
7
7
  import { currentLoad } from "../../domain/limits.ts";
8
- import { type AgentPolicy, type Board, labelOf, type Note } from "../../domain/model.ts";
8
+ import { type AgentPolicy, type Board, type Job, labelOf, type Note } from "../../domain/model.ts";
9
9
  import { agentList, LATEST_NOTES } from "../../domain/overview.ts";
10
10
  import { orderWaiting, type Queues, slotsOf } from "../../domain/queues.ts";
11
11
 
@@ -126,8 +126,13 @@ export function formatStatus(board: Board, config: Config, me: string, now: numb
126
126
  /** One block per queue: running jobs, then waiting jobs in the order they will start. */
127
127
  export function formatQueues(board: Board, queues: Queues, now: number): string[] {
128
128
  const lines: string[] = [];
129
- const who = (owner: string | null) =>
130
- owner === null ? "outside omp" : `session ${labelOf(board, owner) ?? "(ended)"}`;
129
+ // whose job: the session, and the agent when recorded ("session A/WaveImport")
130
+ const who = (j: Job) =>
131
+ (j.owner === null ? "outside omp" : `session ${labelOf(board, j.owner) ?? "(ended)"}`) +
132
+ (j.agent ? `/${j.agent}` : "");
133
+ // what it is: the job's key ("<checkout> <recipe> <args>") reads better than the command's full path; the command
134
+ // when there is no key
135
+ const what = (j: Job) => j.key || j.command;
131
136
  for (const [name, q] of Object.entries(queues)) {
132
137
  const running = board.jobs.filter(j => j.queue === name && j.state === "running");
133
138
  const waiting = orderWaiting(q, name, board, now);
@@ -135,14 +140,14 @@ export function formatQueues(board: Board, queues: Queues, now: number): string[
135
140
  const lowered = slots < q.slots && !board.pause ? ` (lowered from ${q.slots} under load)` : "";
136
141
  lines.push(` ${name} (${q.order}): ${running.length}/${slots} running${lowered}, ${waiting.length} waiting`);
137
142
  for (const j of running)
138
- lines.push(` running ${j.id} ${who(j.owner)} ${minutes(now - (j.startedAt ?? now))} ${j.command}`);
143
+ lines.push(` running ${j.id} ${who(j)} ${minutes(now - (j.startedAt ?? now))} ${what(j)}`);
139
144
  for (const [i, j] of waiting.entries()) {
140
145
  const extras = [
141
146
  j.lane === q.lanes.at(-1) ? "" : `lane ${j.lane}`,
142
147
  j.priority ? `priority ${j.priority}` : "",
143
148
  ].filter(Boolean);
144
149
  lines.push(
145
- ` #${i + 1} ${j.id} ${who(j.owner)} waited ${minutes(now - j.since)}${extras.length ? ` (${extras.join(", ")})` : ""} ${j.command}`,
150
+ ` #${i + 1} ${j.id} ${who(j)} waited ${minutes(now - j.since)}${extras.length ? ` (${extras.join(", ")})` : ""} ${what(j)}`,
146
151
  );
147
152
  }
148
153
  }
@@ -17,6 +17,8 @@ export interface JobSpec {
17
17
  id: string;
18
18
  queue: string;
19
19
  owner: string | null;
20
+ /** omp's id of the agent whose command this is, for display. */
21
+ agent?: string;
20
22
  lane?: string;
21
23
  priority: number;
22
24
  key?: string;
package/src/config.ts CHANGED
@@ -26,6 +26,12 @@ export interface Config {
26
26
  commands: CommandRule[];
27
27
  /** Build and test queues, machine-wide within the scope. */
28
28
  queues: Queues;
29
+ /**
30
+ * Start each agent bash command with `export OMP_COORD_AGENT=<agent id>;` so queued jobs record whose they are.
31
+ * Off by default: the rewritten command no longer matches omp's `bash.patterns` rules and replaces other
32
+ * extensions' rewrites of the same call.
33
+ */
34
+ tagAgentCommands: boolean;
29
35
  /** Port ranges and scratch folders handed to sessions and their agents. */
30
36
  resources: { ports: PortPolicy | undefined; scratch: string | undefined };
31
37
  /** Retention of the event log and the decision log. */
@@ -185,6 +191,7 @@ export function parseConfig(raw: unknown, source: string, hash = "none"): Config
185
191
  },
186
192
  commands,
187
193
  queues,
194
+ tagAgentCommands: flag(root.tagAgentCommands ?? false, "tagAgentCommands", fail),
188
195
  resources: { ports, scratch },
189
196
  history: {
190
197
  maxAgeMs: count(history.retentionDays ?? 30, "history.retentionDays", fail) * 24 * 3_600_000,
@@ -205,6 +212,11 @@ function list(v: unknown, field: string, fail: Fail): unknown[] {
205
212
  return v;
206
213
  }
207
214
 
215
+ function flag(v: unknown, field: string, fail: Fail): boolean {
216
+ if (typeof v !== "boolean") fail(field, "must be true or false");
217
+ return v as boolean;
218
+ }
219
+
208
220
  function str(v: unknown, field: string, fail: Fail): string {
209
221
  if (typeof v !== "string" || v.trim() === "") return fail(field, "must be a non-empty string");
210
222
  return v;
@@ -54,3 +54,12 @@ export function decideCommand(rules: CommandRule[], command: string, runner: Run
54
54
  function quote(word: string): string {
55
55
  return `'${word.replaceAll("'", `'\\''`)}'`;
56
56
  }
57
+
58
+ /**
59
+ * The command with the id of the agent running it exported first, so a queued job records whose it is
60
+ * (`omp-coord run` reads `OMP_COORD_AGENT`). An export, not a `VAR=… cmd` prefix, so it reaches every command of a
61
+ * compound line; each agent's next command sets it again.
62
+ */
63
+ export function withAgent(agentId: string, command: string): string {
64
+ return `export OMP_COORD_AGENT=${quote(agentId)}; ${command}`;
65
+ }
@@ -158,6 +158,9 @@ export interface Job {
158
158
  queue: string;
159
159
  /** The omp session whose agent started it, or null (a terminal, a script). Fair share is per owner. */
160
160
  owner: string | null;
161
+ /** omp's id of the agent whose command queued it ("Main" for the main agent), or undefined (outside omp, or an
162
+ * older omp-coord that did not record it). For display only. */
163
+ agent: string | undefined;
161
164
  lane: string;
162
165
  priority: number;
163
166
  /** Jobs with the same key are duplicates (e.g. "<checkout> ci"). */
@@ -51,6 +51,8 @@ export interface JobView {
51
51
  priority: number;
52
52
  key: string | null;
53
53
  command: string;
54
+ /** omp's id of the agent whose command queued it ("Main" for the main agent); null outside omp or when not recorded. */
55
+ agent: string | null;
54
56
  state: "running" | "waiting";
55
57
  /** When it joined the line (a superseding job keeps the place of the one it replaced). */
56
58
  queuedAt: number;
@@ -136,6 +138,7 @@ export function statusView(board: Board, config: ScopeConfig, now: number): Stat
136
138
  priority: j.priority,
137
139
  key: j.key ?? null,
138
140
  command: j.command,
141
+ agent: j.agent ?? null,
139
142
  state: position === null ? "running" : "waiting",
140
143
  queuedAt: j.since,
141
144
  startedAt: j.startedAt ?? null,
@@ -43,6 +43,7 @@ export interface JobRequest {
43
43
  id: string;
44
44
  queue: string;
45
45
  owner: string | null;
46
+ agent?: string;
46
47
  lane?: string;
47
48
  priority: number;
48
49
  key?: string;
@@ -81,6 +82,7 @@ export function enqueueJob(board: Board, queues: Queues, req: JobRequest): Enque
81
82
  id: req.id,
82
83
  queue: req.queue,
83
84
  owner: req.owner,
85
+ agent: req.agent,
84
86
  lane,
85
87
  priority: req.priority,
86
88
  key: req.key,
@@ -97,6 +99,7 @@ export function enqueueJob(board: Board, queues: Queues, req: JobRequest): Enque
97
99
  lane,
98
100
  priority: req.priority,
99
101
  ...(req.key === undefined ? {} : { key: req.key }),
102
+ ...(req.agent === undefined ? {} : { agent: req.agent }),
100
103
  });
101
104
  schedule(board, queues, req.now);
102
105
  return { ok: true };