@staix/agent-hub 0.8.0 → 0.8.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/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/docs/operations.md +12 -7
- package/docs/quickstart.md +1 -1
- package/docs/smoke.md +24 -0
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1 -1
- package/src/hub/daemon.ts +14 -4
- package/src/hub/lifecycle.ts +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the previous repository, archived on 2026-09-30 when this repository's history was rewritten; the one exception is the open smoke-check issue, formerly #12, which moved here as #1. Numbers in newer entries refer to this repository.
|
|
4
4
|
|
|
5
|
+
## 0.8.1
|
|
6
|
+
|
|
7
|
+
- Upgrading a running 0.7.x hub that has a task on its board no longer stops at verification ("queue, manual pause, task board or budget preservation was not verified"): the 0.8.0 target digested its tasks with the new `plan` field, which the 0.7.x source never had. The target now also accepts the digest in the source's shape while every plan is empty; any real change to the board still fails the check. Use the 0.8.1 coordinator, not 0.8.0's, for such an upgrade (#57).
|
|
8
|
+
- `ahub kill` returns only once the hub has released its registry claim, so an `ahub projects remove` right after it no longer refuses, and an `ahub up` right after it no longer reads the project as starting and starts nothing (#58).
|
|
9
|
+
|
|
5
10
|
## 0.8.0
|
|
6
11
|
|
|
7
12
|
- Plans and completed-change notices: `hub_task_propose` and `hub_task_accept` take a `plan` (paths, symbols, signatures, insertion points), overlaps also count plan paths and shared symbols, the owners of overlapping tasks get the plan as a ride-along line, and when a task is done they get a notice of the changed files, signatures and summary. PII tasks are left out on both sides, and a plan matching a PII pattern makes a new task a PII task or is refused on an ordinary one. Overlap mentions, ride-alongs, completed-change notices and overlap events leave out any name that matches a PII pattern, and model-written titles, refs and plan items are folded onto one line. During a PII turn the local worker can no longer finish, review or accept-with-plan an ordinary task, which would carry its words to other peers and claude-mem (#31).
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Native multi-agent hub for one developer's machine: Claude Code, Codex, Kimi Cod
|
|
|
4
4
|
hub-owned local-LLM worker collaborate as peers in independent project directories, with
|
|
5
5
|
task-aware model routing (Switchyard) in front of a self-hosted gateway (OmniRoute).
|
|
6
6
|
|
|
7
|
-
Status: 0.8.
|
|
7
|
+
Status: 0.8.1, control protocol 10. Durable delivery records distinguish queued
|
|
8
8
|
work from uncertain execution. The [smoke checklist](docs/smoke.md) records
|
|
9
9
|
verified paths and remaining prerequisites.
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@ cd <your project> && ahub init && ahub up && ahub tail
|
|
|
28
28
|
Or install the same version from GitHub:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.8.
|
|
31
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.8.1 && ahub setup
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
The installed commands remain `ahub` and `agent-hub`.
|
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.8.
|
|
3
|
+
This guide describes ahub 0.8.1 and control protocol 10. Live verification
|
|
4
4
|
results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
|
|
5
5
|
|
|
6
6
|
## Install and start
|
|
@@ -302,8 +302,8 @@ source and carries every recovery fix released up to it. Protocol 8 and older
|
|
|
302
302
|
project directory, without replacing the global CLI first:
|
|
303
303
|
|
|
304
304
|
```bash
|
|
305
|
-
bunx --package @staix/agent-hub@0.8.
|
|
306
|
-
bunx --package @staix/agent-hub@0.8.
|
|
305
|
+
bunx --package @staix/agent-hub@0.8.1 ahub upgrade --to 0.8.1 --dry-run
|
|
306
|
+
bunx --package @staix/agent-hub@0.8.1 ahub upgrade --to 0.8.1 --yes
|
|
307
307
|
```
|
|
308
308
|
|
|
309
309
|
| Running now | Coordinator to use |
|
|
@@ -313,7 +313,12 @@ bunx --package @staix/agent-hub@0.8.0 ahub upgrade --to 0.8.0 --yes
|
|
|
313
313
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
314
314
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
315
315
|
|
|
316
|
-
Do not use
|
|
316
|
+
Do not use the 0.8.0 coordinator for a running 0.7.x hub with tasks on its board:
|
|
317
|
+
its verification never matches the board and the operation stays blocked (fixed
|
|
318
|
+
in 0.8.1). Such a blocked operation can neither resume nor abort, and its lock
|
|
319
|
+
refuses `up` and `kill` for every project; the [smoke ledger](smoke.md) (0.7.11
|
|
320
|
+
to 0.8.0) records the manual cleanup. Do not use an older installed CLI as the
|
|
321
|
+
coordinator. A 0.6.x CLI cannot target
|
|
317
322
|
protocol 10: its plan does not check the target's protocol, so the dry-run shows
|
|
318
323
|
no blocker, and `--yes` stops at staging ("target protocol requires a newer
|
|
319
324
|
coordinator") with an operation left to clear by `ahub recovery abort <id>`. An
|
|
@@ -325,19 +330,19 @@ The coordinator verifies and retains the exact target package, preserves its
|
|
|
325
330
|
own source, and promotes the global CLI only after restored projects pass
|
|
326
331
|
readback.
|
|
327
332
|
|
|
328
|
-
Once the installed CLI is 0.8.
|
|
333
|
+
Once the installed CLI is 0.8.1, review the current project or all registered
|
|
329
334
|
projects first:
|
|
330
335
|
|
|
331
336
|
```bash
|
|
332
337
|
ahub restart --dry-run
|
|
333
|
-
ahub upgrade --to 0.8.
|
|
338
|
+
ahub upgrade --to 0.8.1 --dry-run
|
|
334
339
|
```
|
|
335
340
|
|
|
336
341
|
Apply only after reviewing the plan:
|
|
337
342
|
|
|
338
343
|
```bash
|
|
339
344
|
ahub restart --yes
|
|
340
|
-
ahub upgrade --to 0.8.
|
|
345
|
+
ahub upgrade --to 0.8.1 --yes
|
|
341
346
|
ahub recovery status <operation-id>
|
|
342
347
|
ahub recovery resume <operation-id>
|
|
343
348
|
ahub recovery abort <operation-id>
|
package/docs/quickstart.md
CHANGED
package/docs/smoke.md
CHANGED
|
@@ -859,3 +859,27 @@ the claim overlap warnings (0.7.6 and later), then the owner decides in issue #8
|
|
|
859
859
|
and that Codex's running session no longer knows the turn (ask it about the
|
|
860
860
|
reverted turn): the schema says `thread/revert` changes only the saved history.
|
|
861
861
|
|
|
862
|
+
## 0.7.11 to 0.8.0 attended upgrade (2026-10-01)
|
|
863
|
+
|
|
864
|
+
A scratch project (a git work tree whose `.agenthub/config.json` predates 0.8.0
|
|
865
|
+
and has no `snapshots` block) ran a 0.7.11 hub with one proposed task and no
|
|
866
|
+
peers attached.
|
|
867
|
+
|
|
868
|
+
- `bunx --package @staix/agent-hub@0.8.0 ahub upgrade --to 0.8.0 --dry-run`
|
|
869
|
+
exited 0: one project, source 0.7.11, protocol 10, no blockers.
|
|
870
|
+
- `--yes` prepared and committed the source, started the 0.8.0 target and
|
|
871
|
+
reached the restored-peers phase (no peers were attached), then stopped at
|
|
872
|
+
verification: `phase: blocked`, "queue,
|
|
873
|
+
manual pause, task board or budget preservation was not verified". The
|
|
874
|
+
global CLI stayed at 0.7.11.
|
|
875
|
+
- Cause: the 0.7.11 source digested its task rows without `plan`, and the
|
|
876
|
+
0.8.0 target's rows carry `plan: {}` after the #31 migration, so any board
|
|
877
|
+
with a task failed the check. Fixed in 0.8.1 (#57).
|
|
878
|
+
- The blocked operation could not resume (the same target fails the same
|
|
879
|
+
check) or abort (the source was already stopped), and its recovery lock
|
|
880
|
+
refused `up` and `kill` for every project. The scratch hub was stopped under
|
|
881
|
+
the operation's environment, and the operation was marked cancelled with its
|
|
882
|
+
lock released: an operator step, not a designed path.
|
|
883
|
+
- The release gate for v0.8.0 (Actions run 36807880478) failed once on
|
|
884
|
+
"removing a running project is refused"; the rerun passed and published. Cause: `ahub kill` returned before
|
|
885
|
+
the hub released its registry claim. Fixed in 0.8.1 (#58).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-hub",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Young Joon Lee",
|
|
@@ -15727,7 +15727,7 @@ class ControlClient {
|
|
|
15727
15727
|
// package.json
|
|
15728
15728
|
var package_default = {
|
|
15729
15729
|
name: "@staix/agent-hub",
|
|
15730
|
-
version: "0.8.
|
|
15730
|
+
version: "0.8.1",
|
|
15731
15731
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
15732
15732
|
license: "MIT",
|
|
15733
15733
|
type: "module",
|
package/src/hub/daemon.ts
CHANGED
|
@@ -673,12 +673,22 @@ export async function startDaemon(opts: DaemonOptions) {
|
|
|
673
673
|
return [id, row];
|
|
674
674
|
}));
|
|
675
675
|
const digest = (value: unknown) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
676
|
-
|
|
676
|
+
// `prePlan`: the board as a hub from before the plan column (#31, 0.8.0) digested it. An empty plan is left out, so an
|
|
677
|
+
// upgrade from such a hub still verifies the board it was handed; a real plan never matches that shape.
|
|
678
|
+
const integrity = (prePlan = false) => {
|
|
677
679
|
const queues = Object.fromEntries(Object.keys(bus.snapshot().queues).sort().map((id) => [id, bus.queueIds(id)]));
|
|
678
|
-
const
|
|
680
|
+
const tasks = board.list().sort((a, b) => a.id - b.id);
|
|
681
|
+
const boardState = prePlan ? tasks.map(({ plan, ...t }) => (plan && Object.keys(plan).length ? { ...t, plan } : t)) : tasks;
|
|
679
682
|
const budgetState = budget.persistedPauseDigestRows().sort((a, b) => a.peer.localeCompare(b.peer));
|
|
680
683
|
return { queues, manualPaused: [...manualPaused].sort(), boardDigest: digest(boardState), budgetDigest: digest(budgetState) };
|
|
681
684
|
};
|
|
685
|
+
/** The integrity in the shape `expected` was recorded in: the current one unless only the pre-plan shape matches. */
|
|
686
|
+
const integrityAs = (expected: unknown) => {
|
|
687
|
+
const now = integrity();
|
|
688
|
+
if (!expected || JSON.stringify(expected) === JSON.stringify(now)) return now;
|
|
689
|
+
const old = integrity(true);
|
|
690
|
+
return JSON.stringify(expected) === JSON.stringify(old) ? old : now;
|
|
691
|
+
};
|
|
682
692
|
const status = () => ({
|
|
683
693
|
projectId,
|
|
684
694
|
instanceId,
|
|
@@ -1200,7 +1210,7 @@ export async function startDaemon(opts: DaemonOptions) {
|
|
|
1200
1210
|
...([...bus.peers].filter(([, peer]) => peer.state === "busy").map(([id]) => `${id} is busy`)),
|
|
1201
1211
|
...(permissions.size ? ["pending approvals"] : []),
|
|
1202
1212
|
],
|
|
1203
|
-
integrity: { current: integrity
|
|
1213
|
+
integrity: { current: integrityAs(restored?.integrity), ...(restored?.integrity ? { expected: restored.integrity } : {}) },
|
|
1204
1214
|
peers: Object.fromEntries([...(recoveryPeerSnapshot ?? []), ...Object.values(recoveryPeers()).filter((peer) => !(recoveryPeerSnapshot ?? []).some((saved) => saved.id === peer.id))].map((peer) => {
|
|
1205
1215
|
const now = recoveryPeers()[peer.id];
|
|
1206
1216
|
if (recoveryPhase === "restored" || recoveryPhase === "released") return [peer.id, now ?? { id: peer.id, state: "offline", queueIds: bus.queueIds(peer.id) }];
|
|
@@ -1298,7 +1308,7 @@ export async function startDaemon(opts: DaemonOptions) {
|
|
|
1298
1308
|
const missing = (recoveryPeerSnapshot ?? []).filter((saved) => saved.state !== "offline" && !manualPaused.has(saved.id) && !budget.record(saved.id) && (!current[saved.id] || current[saved.id]!.state === "offline"));
|
|
1299
1309
|
if (missing.length) return recoveryError(`required peers are not attached: ${missing.map((peer) => peer.id).join(", ")}`);
|
|
1300
1310
|
if (!recoveryReady()) return recoveryError("required peers or approvals are not ready");
|
|
1301
|
-
if (!restored?.integrity || JSON.stringify(restored.integrity) !== JSON.stringify(integrity
|
|
1311
|
+
if (!restored?.integrity || JSON.stringify(restored.integrity) !== JSON.stringify(integrityAs(restored.integrity))) {
|
|
1302
1312
|
return recoveryError("queue, pause, task board or budget integrity changed during recovery");
|
|
1303
1313
|
}
|
|
1304
1314
|
archiveRestartSnapshot(opts.stateDir, op);
|
package/src/hub/lifecycle.ts
CHANGED
|
@@ -179,6 +179,15 @@ export async function startProject(project: Project, options: { unattended?: boo
|
|
|
179
179
|
} finally { registry.close(); }
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/** Whether `instanceId` still holds the project's registry claim, counted as held while its process may be alive. */
|
|
183
|
+
function claimHeld(id: string, instanceId: string): boolean {
|
|
184
|
+
const registry = new Registry();
|
|
185
|
+
try {
|
|
186
|
+
const row = registry.get(id);
|
|
187
|
+
return row?.instanceId === instanceId && (row.pid === null || processAlive(row.pid) !== false);
|
|
188
|
+
} finally { registry.close(); }
|
|
189
|
+
}
|
|
190
|
+
|
|
182
191
|
export async function stopProject(project: Project, expectedInstance?: string): Promise<void> {
|
|
183
192
|
assertLifecycleAvailable();
|
|
184
193
|
const inspection = await inspectProject(project);
|
|
@@ -194,8 +203,10 @@ export async function stopProject(project: Project, expectedInstance?: string):
|
|
|
194
203
|
const deadline = Date.now() + 10_000;
|
|
195
204
|
while (Date.now() < deadline) {
|
|
196
205
|
const current = readControl(project.stateDir);
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
// The daemon removes its manifest only after owned children stop; its process releases the registry claim just
|
|
207
|
+
// after that. Until then `projects remove` refuses and `up` reads the project as starting, so wait for both.
|
|
208
|
+
if (!current && !claimHeld(project.id, instanceId)) return;
|
|
209
|
+
if (current && current.instanceId !== instanceId) throw new Error("a new hub instance started; it was not stopped");
|
|
199
210
|
await Bun.sleep(100);
|
|
200
211
|
}
|
|
201
212
|
throw new Error("shutdown is still pending; state and ownership were retained");
|