omp-conductor 0.3.16 → 0.3.18
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/README.md +15 -10
- package/package.json +1 -1
- package/skills/conductor-update/SKILL.md +112 -90
- package/src/tracker/github.ts +40 -19
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ The package ships three deployables, plus two skills:
|
|
|
39
39
|
| Standalone daemon | `omp-conductor` binary | The dispatch loop, managed as a background process (`start` / `stop` / `restart`) with a `/healthz` endpoint for a supervisor. |
|
|
40
40
|
| Orchestrator heartbeat | omp extension, activated by `.conductor-tick.json` | Prompts a 24/7 orchestrator session on a fixed interval so its standing loop actually runs, and marks the session stalled when its prompts stop being consumed. Inert in every other session — including a second session opened in the fleet's own directory. See [Orchestrator tick](#orchestrator-tick). |
|
|
41
41
|
| Onboarding skill | `skill://conductor-onboarding` | Directs an omp session to interview you, read your repos for real CI gates, and tailor `ORCHESTRATOR.md` — then finish through the wizard. Discovered automatically once the plugin is installed. See [Onboarding](#onboarding). |
|
|
42
|
-
| Update skill | `skill://conductor-update` | Treats the
|
|
42
|
+
| Update skill | `skill://conductor-update` | Treats the Bun-global CLI, omp plugin, and Herdr plugin as one operation: pause claims, drain, install one pinned release, reload, verify twice, and restore the prior pause state. See [Updating](#updating). |
|
|
43
43
|
|
|
44
44
|
The first two are thin wrappers over the same `daemon.ts`, so the plugin and the
|
|
45
45
|
CLI cannot disagree about what a cap means or where the state lives. Claiming is
|
|
@@ -169,15 +169,20 @@ Also required on the host:
|
|
|
169
169
|
## Updating
|
|
170
170
|
|
|
171
171
|
Say “update conductor” from an operator shell or maintenance omp session outside
|
|
172
|
-
the target `herdr-fleet.service`. The bundled
|
|
173
|
-
the installed and registry versions,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
the
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
172
|
+
the target `herdr-fleet.service`. That is the whole operator interface. The bundled
|
|
173
|
+
`skill://conductor-update` discovers the installed and registry versions, pauses
|
|
174
|
+
new claims while active work drains, and pins one release across the Bun-global
|
|
175
|
+
CLI, omp plugin, and Herdr plugin. It converts an old local Herdr link to a managed
|
|
176
|
+
checkout when necessary, reloads the Herdr service and dispatch daemon, verifies
|
|
177
|
+
the layered status twice, and restores the original dispatch state.
|
|
178
|
+
|
|
179
|
+
Ticks remain in their existing armed or disarmed state, so an ordinary update
|
|
180
|
+
does not halt the exact pane or require another Telegram arm challenge. Any
|
|
181
|
+
installation, reload, or verification failure leaves dispatch paused instead of
|
|
182
|
+
bringing up a mixed fleet. The skill does not publish npm or edit an install root.
|
|
183
|
+
It also refuses to run from the fleet pane that Herdr must restart: an updater
|
|
184
|
+
that kills itself cannot verify the result. With skill commands enabled, invoke
|
|
185
|
+
it directly with:
|
|
181
186
|
|
|
182
187
|
```text
|
|
183
188
|
/skill:conductor-update
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
|
@@ -1,157 +1,179 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conductor-update
|
|
3
|
-
description:
|
|
3
|
+
description: Update an installed conductor fleet through one operator request. Use when the user asks to update, upgrade, refresh, reinstall, or deploy conductor. Pins one npm release across the Bun-global CLI, omp plugin, and Herdr recovery plugin; converts a local Herdr link when needed; pauses claims, reloads processes, verifies the fleet, and restores the operator's prior pause state.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Update a conductor fleet
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The operator interface is one request: **“update conductor.”** Do not hand them
|
|
9
|
+
the implementation as a checklist unless execution is blocked.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
- `herdr-conductor` comes from `TerrifiedBug/conductor/herdr` and owns exact-pane recovery.
|
|
11
|
+
One release has three installed surfaces:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
1. Bun-global `omp-conductor`: CLI and systemd daemon source.
|
|
14
|
+
2. omp npm plugin `omp-conductor`: slash command and heartbeat loaded by sessions.
|
|
15
|
+
3. Herdr plugin `herdr-conductor`: exact-pane recovery, pinned to the npm
|
|
16
|
+
release's `gitHead`.
|
|
17
|
+
|
|
18
|
+
This skill performs the whole swap. It does not publish npm, merge, tag, edit an
|
|
19
|
+
install root, or update unrelated Bun packages.
|
|
16
20
|
|
|
17
21
|
## Safety boundary
|
|
18
22
|
|
|
19
|
-
Run
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
Run from an operator shell or maintenance omp session that is not hosted by the
|
|
24
|
+
target `herdr-fleet.service`. If restarting that unit would kill this updater,
|
|
25
|
+
move to an external session first. Never print npm, Telegram, or bot credentials.
|
|
26
|
+
|
|
27
|
+
The normal update is:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
inspect → pause claims → drain → pinned installs → reload → verify → restore pause state
|
|
31
|
+
```
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
authentication files.
|
|
33
|
+
Ticks stay in their existing armed or disarmed state. Do not use `hold`,
|
|
34
|
+
`halt --pane`, `disarm`, `arm`, recovery pins, `pkill`, or manual install-root
|
|
35
|
+
edits for a healthy update.
|
|
29
36
|
|
|
30
|
-
## 1.
|
|
37
|
+
## 1. Resolve one release and inspect every surface
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Use the registry latest unless the operator names another published version:
|
|
33
40
|
|
|
34
41
|
```bash
|
|
35
|
-
omp-conductor --version
|
|
36
42
|
version=$(npm view omp-conductor version)
|
|
37
43
|
gitHead=$(npm view "omp-conductor@$version" gitHead)
|
|
38
44
|
npm view "omp-conductor@$version" version gitHead --json
|
|
39
|
-
omp plugin list --json
|
|
40
|
-
omp-conductor status [--project NAME]
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
and require a full commit SHA: the npm spec and Herdr ref below are both pinned,
|
|
46
|
-
so a concurrent release cannot mix two versions. If npm has no newer version, do
|
|
47
|
-
not churn the fleet: report that it is current. Confirm that `herdr` and
|
|
48
|
-
`systemctl` are present before taking anything down when status says Herdr
|
|
49
|
-
manages the pane.
|
|
50
|
-
|
|
51
|
-
## 2. Quiesce without losing work
|
|
47
|
+
Require `$version` to be nonempty and `$gitHead` to be a full commit SHA before
|
|
48
|
+
changing state.
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
Inspect, without mutating:
|
|
54
51
|
|
|
55
52
|
```bash
|
|
56
|
-
omp-conductor
|
|
53
|
+
omp-conductor --version
|
|
54
|
+
omp plugin list --json
|
|
55
|
+
herdr --session "$session" plugin list
|
|
56
|
+
omp-conductor status [--project NAME]
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
does not include discarding work.
|
|
59
|
+
Honor the installation's existing Herdr session and `HERDR_CONFIG_PATH`; discover
|
|
60
|
+
them from the running unit/config rather than assuming `fleet` or a path.
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
fail-closed path:
|
|
62
|
+
Record:
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
- Bun-global CLI version;
|
|
65
|
+
- omp plugin version;
|
|
66
|
+
- Herdr plugin source: GitHub revision, `local:<path>`, or missing;
|
|
67
|
+
- initial dispatch state: running, paused, or stopped;
|
|
68
|
+
- initial ticks state, which this update must not change;
|
|
69
|
+
- active runs and layered health.
|
|
70
|
+
|
|
71
|
+
Report “already current” only when **all** of these are true:
|
|
72
|
+
|
|
73
|
+
- Bun-global CLI version equals `$version`;
|
|
74
|
+
- omp plugin version equals `$version`;
|
|
75
|
+
- Herdr source revision equals `$gitHead`;
|
|
76
|
+
- layered status is healthy for the fleet's configured topology.
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
A current npm version with a linked, missing, or stale Herdr plugin is a partial
|
|
79
|
+
update, not a no-op. If all versions match but status is unhealthy, diagnose the
|
|
80
|
+
reported layer; never call an unhealthy fleet current.
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
## 2. Pause claims and drain
|
|
83
|
+
|
|
84
|
+
If dispatch was running:
|
|
75
85
|
|
|
76
86
|
```bash
|
|
77
|
-
|
|
87
|
+
omp-conductor pause [--project NAME]
|
|
78
88
|
```
|
|
79
89
|
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
If it was already paused, preserve that state. If it was stopped, do not start it
|
|
91
|
+
later merely because packages were updated.
|
|
92
|
+
|
|
93
|
+
Wait for `active runs (none)`. Never kill workers for an update. If a run does not
|
|
94
|
+
drain, stop and report it; leave any pause this skill added in place.
|
|
82
95
|
|
|
83
|
-
## 3. Replace
|
|
96
|
+
## 3. Replace all three surfaces with the pinned release
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
Use exact versions. A broad `bun update` is prohibited.
|
|
86
99
|
|
|
87
100
|
```bash
|
|
101
|
+
bun add -g "omp-conductor@$version"
|
|
88
102
|
omp plugin install "omp-conductor@$version"
|
|
89
103
|
```
|
|
90
104
|
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
For Herdr, inspect the source found in step 1:
|
|
106
|
+
|
|
107
|
+
- `local:<path>`: unlink the plugin id first;
|
|
108
|
+
- GitHub-managed or missing: do not unlink.
|
|
109
|
+
|
|
110
|
+
Then install the exact npm release commit. Herdr requires the source argument
|
|
111
|
+
before its options:
|
|
93
112
|
|
|
94
113
|
```bash
|
|
114
|
+
herdr plugin unlink herdr-conductor # only when step 1 reported local:<path>
|
|
95
115
|
herdr plugin install TerrifiedBug/conductor/herdr --ref "$gitHead" --yes
|
|
96
116
|
```
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
If any install fails, do not reload processes and do not restore dispatch.
|
|
119
|
+
Leave the fleet paused, report the failed surface, and give the one retry command.
|
|
120
|
+
The still-running processes keep their already-loaded code until a successful
|
|
121
|
+
reload.
|
|
102
122
|
|
|
103
|
-
## 4.
|
|
123
|
+
## 4. Reload the installed code
|
|
104
124
|
|
|
105
|
-
|
|
125
|
+
When Herdr is systemd-managed, restart its fleet unit. This reloads the managed
|
|
126
|
+
recovery plugin and causes exact-identity recovery to resume the orchestrator pane
|
|
127
|
+
with the new omp extension:
|
|
106
128
|
|
|
107
129
|
```bash
|
|
108
|
-
|
|
109
|
-
omp-conductor start [--project NAME]
|
|
130
|
+
systemctl restart herdr-fleet.service
|
|
110
131
|
```
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
starts the dispatch daemon after a real `/healthz` check. Herdr then recovers the
|
|
114
|
-
exact orchestrator pane and requests an immediate tick; the verification below,
|
|
115
|
-
not the `start` command alone, proves that recovery completed.
|
|
133
|
+
Wait until Herdr reports its unit active and the exact configured agent live.
|
|
116
134
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
so restore unattended operation through the existing Telegram proof:
|
|
135
|
+
If the dispatch daemon was running or paused initially, restart it so its process
|
|
136
|
+
and source-integrity baseline use the new Bun-global package:
|
|
120
137
|
|
|
121
138
|
```bash
|
|
122
|
-
omp-conductor
|
|
139
|
+
omp-conductor restart [--project NAME]
|
|
123
140
|
```
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
hand and never treat an outbound challenge as proof.
|
|
142
|
+
Do not substitute `start` when the daemon was initially stopped.
|
|
127
143
|
|
|
128
|
-
## 5. Verify
|
|
144
|
+
## 5. Verify before restoring claims
|
|
129
145
|
|
|
130
|
-
|
|
146
|
+
Check every surface again:
|
|
131
147
|
|
|
132
148
|
```bash
|
|
133
149
|
omp-conductor --version
|
|
134
|
-
npm view "omp-conductor@$version" version gitHead --json
|
|
135
150
|
omp plugin list --json
|
|
136
|
-
herdr plugin list
|
|
151
|
+
herdr --session "$session" plugin list
|
|
137
152
|
omp-conductor status [--project NAME]
|
|
138
153
|
systemctl is-active herdr-fleet.service
|
|
139
154
|
```
|
|
140
155
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
Require:
|
|
157
|
+
|
|
158
|
+
- global CLI and omp plugin both equal `$version`;
|
|
159
|
+
- Herdr source equals `TerrifiedBug/conductor` at `$gitHead`;
|
|
160
|
+
- exact pane is live and recovery is clear;
|
|
161
|
+
- Herdr and daemon health are OK when managed;
|
|
162
|
+
- ticks equal their initial state;
|
|
163
|
+
- active runs remain empty;
|
|
164
|
+
- dispatch remains paused if this skill paused it.
|
|
165
|
+
|
|
166
|
+
Run status a second time after recovery settles. Any failed check leaves dispatch
|
|
167
|
+
paused and is reported as a partial update.
|
|
168
|
+
|
|
169
|
+
Only if dispatch was initially running and every check passed:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
omp-conductor resume [--project NAME]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Verify `dispatch running` once more. If dispatch was initially paused or stopped,
|
|
176
|
+
preserve that state and report it.
|
|
177
|
+
|
|
178
|
+
Finish with one compact result: old → new version, all three installed surfaces,
|
|
179
|
+
final layered status, and whether the original dispatch state was restored.
|
package/src/tracker/github.ts
CHANGED
|
@@ -53,6 +53,12 @@ const CLOSERS_QUERY = `query($owner:String!,$repo:String!,$n:Int!){
|
|
|
53
53
|
}
|
|
54
54
|
}`;
|
|
55
55
|
|
|
56
|
+
const PARENT_QUERY = `query($owner:String!,$repo:String!,$n:Int!){
|
|
57
|
+
repository(owner:$owner,name:$repo){
|
|
58
|
+
issue(number:$n){ parent{ number } }
|
|
59
|
+
}
|
|
60
|
+
}`;
|
|
61
|
+
|
|
56
62
|
/** The `gh api graphql` envelope for {@link CLOSERS_QUERY}. Every level is
|
|
57
63
|
* nullable: a deleted or wrong-numbered issue answers `null`, not an error. */
|
|
58
64
|
interface ClosersResponse {
|
|
@@ -190,27 +196,30 @@ export function prStateFrom(raw: string): PrState | undefined {
|
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
/**
|
|
193
|
-
* Parent number from a
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* "no parent" and skip the epic soft-cap.
|
|
199
|
+
* Parent number from a raw GraphQL response, or undefined when the issue has
|
|
200
|
+
* no parent. Throws when the issue or claimed parent is malformed — admission
|
|
201
|
+
* must not turn an unknown relationship into permission to run siblings.
|
|
197
202
|
*/
|
|
198
203
|
export function parentNumberFrom(raw: string): number | undefined {
|
|
199
|
-
const parsed = JSON.parse(raw) as {
|
|
200
|
-
|
|
201
|
-
|
|
204
|
+
const parsed = JSON.parse(raw) as {
|
|
205
|
+
data?: { repository?: { issue?: { parent?: { number?: unknown } | null } | null } | null };
|
|
206
|
+
};
|
|
207
|
+
const issue = parsed.data?.repository?.issue;
|
|
208
|
+
if (issue == null) throw new Error("unexpected missing issue in parent response");
|
|
209
|
+
if (issue.parent == null) return undefined;
|
|
210
|
+
const n = issue.parent.number;
|
|
202
211
|
if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
|
|
203
212
|
throw new Error(`unexpected parent number ${JSON.stringify(n)}`);
|
|
204
213
|
}
|
|
205
214
|
return n;
|
|
206
215
|
}
|
|
207
216
|
|
|
208
|
-
export function makeTracker(p: ProjectConfig): Tracker {
|
|
217
|
+
export function makeTracker(p: ProjectConfig, runGh: typeof gh = gh): Tracker {
|
|
209
218
|
const repo = p.tracker.repo;
|
|
210
219
|
|
|
211
220
|
return {
|
|
212
221
|
async listReady(): Promise<ReadyIssue[]> {
|
|
213
|
-
const raw = await
|
|
222
|
+
const raw = await runGh([
|
|
214
223
|
"issue",
|
|
215
224
|
"list",
|
|
216
225
|
"--repo",
|
|
@@ -246,7 +255,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
246
255
|
|
|
247
256
|
async addLabel(issue: number, label: string): Promise<void> {
|
|
248
257
|
try {
|
|
249
|
-
await
|
|
258
|
+
await runGh(["issue", "edit", String(issue), "--repo", repo, "--add-label", label]);
|
|
250
259
|
} catch (err) {
|
|
251
260
|
if (!isLabelNoop(err, "add")) throw err;
|
|
252
261
|
}
|
|
@@ -254,7 +263,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
254
263
|
|
|
255
264
|
async removeLabel(issue: number, label: string): Promise<void> {
|
|
256
265
|
try {
|
|
257
|
-
await
|
|
266
|
+
await runGh(["issue", "edit", String(issue), "--repo", repo, "--remove-label", label]);
|
|
258
267
|
} catch (err) {
|
|
259
268
|
if (!isLabelNoop(err, "remove")) throw err;
|
|
260
269
|
}
|
|
@@ -263,25 +272,37 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
263
272
|
async comment(issue: number, body: string): Promise<void> {
|
|
264
273
|
// `--body-file -` reads stdin, so the body is never shell- or argv-
|
|
265
274
|
// mangled and has no length limit worth worrying about.
|
|
266
|
-
await
|
|
275
|
+
await runGh(["issue", "comment", String(issue), "--repo", repo, "--body-file", "-"], body);
|
|
267
276
|
},
|
|
268
277
|
|
|
269
278
|
async close(issue: number): Promise<void> {
|
|
270
|
-
await
|
|
279
|
+
await runGh(["issue", "close", String(issue), "--repo", repo]);
|
|
271
280
|
},
|
|
272
281
|
|
|
273
282
|
async linkParent(child: number, parent: number): Promise<void> {
|
|
274
283
|
// Native sub-issue linkage rather than a body mention: it is what the
|
|
275
284
|
// repo's own epic rollups read, so a human sees the split without us
|
|
276
285
|
// maintaining a second index of it.
|
|
277
|
-
await
|
|
286
|
+
await runGh(["issue", "edit", String(parent), "--repo", repo, "--add-sub-issue", String(child)]);
|
|
278
287
|
},
|
|
279
288
|
|
|
280
289
|
async parentOf(issue: number): Promise<number | undefined> {
|
|
281
|
-
//
|
|
282
|
-
//
|
|
290
|
+
// gh 2.86 cannot expose `parent` through `issue view --json`; its raw
|
|
291
|
+
// GraphQL command can, and is already the adapter's path for PR closers.
|
|
292
|
+
const [owner = "", name = ""] = repo.split("/");
|
|
283
293
|
return parentNumberFrom(
|
|
284
|
-
await
|
|
294
|
+
await runGh([
|
|
295
|
+
"api",
|
|
296
|
+
"graphql",
|
|
297
|
+
"-f",
|
|
298
|
+
`query=${PARENT_QUERY}`,
|
|
299
|
+
"-F",
|
|
300
|
+
`owner=${owner}`,
|
|
301
|
+
"-F",
|
|
302
|
+
`repo=${name}`,
|
|
303
|
+
"-F",
|
|
304
|
+
`n=${issue}`,
|
|
305
|
+
]),
|
|
285
306
|
);
|
|
286
307
|
},
|
|
287
308
|
|
|
@@ -290,7 +311,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
290
311
|
// that spelling, so an empty half means a hand-edited config: `gh` then
|
|
291
312
|
// errors and the caller holds the candidate rather than guessing.
|
|
292
313
|
const [owner = "", name = ""] = repo.split("/");
|
|
293
|
-
const raw = await
|
|
314
|
+
const raw = await runGh([
|
|
294
315
|
"api",
|
|
295
316
|
"graphql",
|
|
296
317
|
"-f",
|
|
@@ -316,7 +337,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
316
337
|
// what the URL already says.
|
|
317
338
|
if (!PR_URL.test(url)) return undefined;
|
|
318
339
|
try {
|
|
319
|
-
return prStateFrom(await
|
|
340
|
+
return prStateFrom(await runGh(["pr", "view", url, "--json", "state", "--jq", ".state"]));
|
|
320
341
|
} catch {
|
|
321
342
|
// Never throws, per the port's contract. A deleted PR, a revoked token
|
|
322
343
|
// and a flaky network all mean "could not tell", and the caller's whole
|