@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.
- package/.omp-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +12 -1
- package/docs/commands.md +2 -1
- package/docs/configuration.md +1 -0
- package/docs/event-log.md +2 -2
- package/docs/queues.md +2 -0
- package/package.json +1 -1
- package/src/adapters/cli/cli.ts +2 -0
- package/src/adapters/omp/extension.ts +7 -2
- package/src/adapters/sqlite/store.ts +23 -2
- package/src/adapters/text/report.ts +10 -5
- package/src/app/job-runner.ts +2 -0
- package/src/config.ts +12 -0
- package/src/domain/commands.ts +9 -0
- package/src/domain/model.ts +3 -0
- package/src/domain/overview.ts +3 -0
- package/src/domain/queues.ts +3 -0
|
@@ -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.
|
|
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.
|
|
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. |
|
package/docs/configuration.md
CHANGED
|
@@ -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
package/src/adapters/cli/cli.ts
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
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
|
|
150
|
+
` #${i + 1} ${j.id} ${who(j)} waited ${minutes(now - j.since)}${extras.length ? ` (${extras.join(", ")})` : ""} ${what(j)}`,
|
|
146
151
|
);
|
|
147
152
|
}
|
|
148
153
|
}
|
package/src/app/job-runner.ts
CHANGED
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;
|
package/src/domain/commands.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/domain/model.ts
CHANGED
|
@@ -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"). */
|
package/src/domain/overview.ts
CHANGED
|
@@ -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,
|
package/src/domain/queues.ts
CHANGED
|
@@ -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 };
|