@nledford/omp-coord 0.2.0 → 0.2.1
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/README.md +7 -2
- package/docs/install.md +24 -17
- package/package.json +2 -5
- package/src/adapters/cli/cli.ts +2 -27
- package/src/adapters/os/process-group.ts +44 -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.1",
|
|
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.1] - 2026-10-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Releases are published to npmjs.com as `@nledford/omp-coord`, with provenance: `omp plugin install @nledford/omp-coord` needs no token or `.npmrc`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- On Linux, where `/proc` cannot be read (some hardened containers), a timed-out job now falls back to signalling its process group instead of failing.
|
|
18
|
+
|
|
9
19
|
## [0.2.0] - 2026-10-26
|
|
10
20
|
|
|
11
21
|
### Added
|
|
@@ -36,6 +46,7 @@ Add each user-visible change under Unreleased as it lands, in one of: Added, Cha
|
|
|
36
46
|
- An SQLite event log per scope, with retention.
|
|
37
47
|
- Packaging as an omp plugin, with a marketplace catalog; queued commands run on omp's own Bun.
|
|
38
48
|
|
|
39
|
-
[Unreleased]: https://github.com/nledford/omp-coord/compare/v0.2.
|
|
49
|
+
[Unreleased]: https://github.com/nledford/omp-coord/compare/v0.2.1...HEAD
|
|
50
|
+
[0.2.1]: https://github.com/nledford/omp-coord/compare/v0.2.0...v0.2.1
|
|
40
51
|
[0.2.0]: https://github.com/nledford/omp-coord/compare/v0.1.0...v0.2.0
|
|
41
52
|
[0.1.0]: https://github.com/nledford/omp-coord/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# omp-coord
|
|
2
2
|
|
|
3
|
+
[](https://github.com/nledford/omp-coord/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@nledford/omp-coord)
|
|
5
|
+
[](https://github.com/can1357/oh-my-pi)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
3
8
|
An [omp](https://github.com/can1357/oh-my-pi) plugin, plus a small command-line runner, that lets several omp sessions on one machine, working in the same project, share one set of agent caps and build/test queues, and talk to each other.
|
|
4
9
|
|
|
5
10
|
- **Shared agent caps.** Subagents count against pools (for example 8 `work` and 2 `short` slots) shared by every session in the project. A spawn over the cap is refused through omp's spawn check, so the cap does not depend on agents remembering to take and free slots.
|
|
@@ -16,7 +21,7 @@ Each session gets a letter and shows `coord B · work 5/8 · short 1/2` in omp's
|
|
|
16
21
|
## Quick start
|
|
17
22
|
|
|
18
23
|
```sh
|
|
19
|
-
omp plugin install
|
|
24
|
+
omp plugin install @nledford/omp-coord
|
|
20
25
|
```
|
|
21
26
|
|
|
22
27
|
Then put a `.omp-coord.json` in the project's top folder:
|
|
@@ -36,7 +41,7 @@ Every omp session started in that folder or below now shares 6 agent slots, and
|
|
|
36
41
|
|
|
37
42
|
| | |
|
|
38
43
|
|---|---|
|
|
39
|
-
| [Install](docs/install.md) |
|
|
44
|
+
| [Install](docs/install.md) | From npm, GitHub, the marketplace or GitHub Packages; updating; the `omp-coord` command. |
|
|
40
45
|
| [Configuration](docs/configuration.md) | Every `.omp-coord.json` field. |
|
|
41
46
|
| [Agent slots](docs/agent-slots.md) | How caps, the line, holds and nested agents work. |
|
|
42
47
|
| [Queues](docs/queues.md) | `omp-coord run`, order, lanes, time limits, agents' commands. |
|
package/docs/install.md
CHANGED
|
@@ -2,43 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
Requires omp 18.12 or later. No dependencies at runtime: the extension and the runner use the Bun built into omp.
|
|
4
4
|
|
|
5
|
-
## From
|
|
5
|
+
## From npm
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
omp plugin install @nledford/omp-coord
|
|
9
|
+
omp plugin install @nledford/omp-coord@0.2.0 # a given release
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Releases are published to npmjs.com from the release workflow, with [provenance](https://docs.npmjs.com/generating-provenance-statements) that ties each package to the commit and workflow that built it (from 0.2.1 on). A machine set up for GitHub Packages (below) maps the `@nledford` scope to GitHub, so this command installs from there instead.
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
## From GitHub
|
|
8
15
|
|
|
9
16
|
```sh
|
|
10
|
-
|
|
11
|
-
cat >> ~/.npmrc <<EOF
|
|
12
|
-
@nledford:registry=https://npm.pkg.github.com
|
|
13
|
-
//npm.pkg.github.com/:_authToken=$(gh auth token)
|
|
14
|
-
EOF
|
|
17
|
+
omp plugin install github:nledford/omp-coord
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
Or through omp's marketplace, which this repository also serves:
|
|
18
21
|
|
|
19
22
|
```sh
|
|
20
|
-
omp plugin
|
|
21
|
-
omp plugin install @
|
|
23
|
+
omp plugin marketplace add nledford/omp-coord
|
|
24
|
+
omp plugin install omp-coord@omp-coord
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
## From GitHub
|
|
27
|
+
## From GitHub Packages
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
Each release is also published to GitHub Packages. GitHub Packages asks for a token to install any npm package, public ones included, so each machine needs one-time setup: a token that can read packages, and the `@nledford` scope pointed at GitHub's registry in `~/.npmrc`.
|
|
27
30
|
|
|
28
31
|
```sh
|
|
29
|
-
|
|
32
|
+
gh auth refresh --scopes read:packages # once: let the GitHub CLI's token read packages
|
|
33
|
+
cat >> ~/.npmrc <<EOF
|
|
34
|
+
@nledford:registry=https://npm.pkg.github.com
|
|
35
|
+
//npm.pkg.github.com/:_authToken=$(gh auth token)
|
|
36
|
+
EOF
|
|
30
37
|
```
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Then install a release, or the newest one:
|
|
33
40
|
|
|
34
41
|
```sh
|
|
35
|
-
omp plugin
|
|
36
|
-
omp plugin install
|
|
42
|
+
omp plugin install @nledford/omp-coord@0.2.0
|
|
43
|
+
omp plugin install @nledford/omp-coord
|
|
37
44
|
```
|
|
38
45
|
|
|
39
46
|
## Updating
|
|
40
47
|
|
|
41
|
-
`omp plugin upgrade @nledford/omp-coord` moves a package or git
|
|
48
|
+
`omp plugin upgrade @nledford/omp-coord` moves a package or git install to the newest release or commit; `omp plugin upgrade omp-coord@omp-coord` moves a marketplace install once a release has raised the catalog's version. Bun remembers the commit a git install resolved to, so installing from git again can bring back an older commit; to pin a release, add its tag: `github:nledford/omp-coord#v0.2.0`. [CHANGELOG.md](../CHANGELOG.md) lists what each release changed.
|
|
42
49
|
|
|
43
50
|
## From a checkout
|
|
44
51
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nledford/omp-coord",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"publishConfig": {
|
|
5
|
-
"registry": "https://npm.pkg.github.com"
|
|
6
|
-
},
|
|
3
|
+
"version": "0.2.1",
|
|
7
4
|
"files": [
|
|
8
5
|
"src",
|
|
9
6
|
"docs",
|
|
@@ -16,7 +13,7 @@
|
|
|
16
13
|
"author": "Nathaniel Ledford",
|
|
17
14
|
"repository": {
|
|
18
15
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/nledford/omp-coord.git"
|
|
16
|
+
"url": "git+https://github.com/nledford/omp-coord.git"
|
|
20
17
|
},
|
|
21
18
|
"keywords": [
|
|
22
19
|
"omp",
|
package/src/adapters/cli/cli.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 2 a usage or configuration error.
|
|
15
15
|
*/
|
|
16
16
|
import { randomBytes } from "node:crypto";
|
|
17
|
-
import { mkdirSync, readdirSync,
|
|
17
|
+
import { mkdirSync, readdirSync, statSync } from "node:fs";
|
|
18
18
|
import { constants, userInfo } from "node:os";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { parseArgs } from "node:util";
|
|
@@ -24,6 +24,7 @@ import { EXIT_TIMED_OUT, runJob } from "../../app/job-runner.ts";
|
|
|
24
24
|
import { type Config, ConfigError, findConfigFile, loadConfig } from "../../config.ts";
|
|
25
25
|
import { MAX_PRIORITY } from "../../domain/queues.ts";
|
|
26
26
|
import { osLoad } from "../os/load.ts";
|
|
27
|
+
import { groupRunning } from "../os/process-group.ts";
|
|
27
28
|
import { BoardStore, stateDir } from "../sqlite/store.ts";
|
|
28
29
|
import { formatFindings, formatInterrupted, formatNotes, formatStats, formatStatus } from "../text/report.ts";
|
|
29
30
|
import { startBeacon, unixLiveness } from "../unix/beacon.ts";
|
|
@@ -309,32 +310,6 @@ function signalNumber(signal: NodeJS.Signals): number {
|
|
|
309
310
|
return constants.signals[signal] ?? 1;
|
|
310
311
|
}
|
|
311
312
|
|
|
312
|
-
/**
|
|
313
|
-
* Whether process group `pgid` still has a member that has not exited. A signal to the group succeeds while it holds
|
|
314
|
-
* only zombies, which is what an exited command's children become in a container whose first process never reaps
|
|
315
|
-
* orphans; on Linux, /proc tells them apart. Elsewhere (macOS has no /proc; launchd reaps orphans) the signal decides.
|
|
316
|
-
*/
|
|
317
|
-
function groupRunning(pgid: number): boolean {
|
|
318
|
-
try {
|
|
319
|
-
process.kill(-pgid, 0);
|
|
320
|
-
} catch {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
if (process.platform !== "linux") return true;
|
|
324
|
-
for (const entry of readdirSync("/proc")) {
|
|
325
|
-
if (!/^\d+$/.test(entry)) continue;
|
|
326
|
-
try {
|
|
327
|
-
// /proc/<pid>/stat: "pid (comm) state ppid pgrp …"; comm may contain spaces and parentheses.
|
|
328
|
-
const stat = readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
329
|
-
const [state, , pgrp] = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
330
|
-
if (Number(pgrp) === pgid && state !== "Z") return true;
|
|
331
|
-
} catch {
|
|
332
|
-
// The process ended while being read.
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
313
|
if (import.meta.main) {
|
|
339
314
|
try {
|
|
340
315
|
process.exitCode = await main(Bun.argv.slice(2));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a process group still has a member that has not exited, for stopping a timed-out job's whole group.
|
|
3
|
+
*
|
|
4
|
+
* A signal to the group succeeds while it holds only zombies, which is what an exited command's children become in a
|
|
5
|
+
* container whose first process never reaps orphans. On Linux, /proc tells them apart. Elsewhere (macOS has no /proc;
|
|
6
|
+
* launchd reaps orphans), or when /proc cannot be read, the signal decides.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
|
|
10
|
+
/** The state and process group of a process, from the text of `/proc/<pid>/stat`. */
|
|
11
|
+
export function parseProcStat(stat: string): { state: string; pgrp: number } | undefined {
|
|
12
|
+
// "pid (comm) state ppid pgrp …": comm may contain spaces and parentheses, so fields start after the last ")".
|
|
13
|
+
const close = stat.lastIndexOf(")");
|
|
14
|
+
if (close === -1) return undefined;
|
|
15
|
+
const [state, , pgrp] = stat.slice(close + 2).split(" ");
|
|
16
|
+
const group = Number(pgrp);
|
|
17
|
+
return state && Number.isInteger(group) ? { state, pgrp: group } : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function groupRunning(pgid: number): boolean {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(-pgid, 0);
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (process.platform !== "linux") return true;
|
|
27
|
+
let pids: string[];
|
|
28
|
+
try {
|
|
29
|
+
pids = readdirSync("/proc").filter(entry => /^\d+$/.test(entry));
|
|
30
|
+
} catch {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
for (const pid of pids) {
|
|
34
|
+
let stat: string;
|
|
35
|
+
try {
|
|
36
|
+
stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
continue; // ended while being read, or hidden
|
|
39
|
+
}
|
|
40
|
+
const p = parseProcStat(stat);
|
|
41
|
+
if (p?.pgrp === pgid && p.state !== "Z") return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|