@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 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.0, control protocol 10. Durable delivery records distinguish queued
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.0 && ahub setup
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`.
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.8.0 and control protocol 10. Live verification
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.0 ahub upgrade --to 0.8.0 --dry-run
306
- bunx --package @staix/agent-hub@0.8.0 ahub upgrade --to 0.8.0 --yes
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 an older installed CLI as the coordinator. A 0.6.x CLI cannot target
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.0, review the current project or all registered
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.0 --dry-run
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.0 --yes
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>
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.8.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.8.1
21
21
  ahub setup
22
22
  ```
23
23
 
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": "@staix/agent-hub",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.8.0",
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.0",
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
- const integrity = () => {
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 boardState = board.list().sort((a, b) => a.id - b.id);
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(), ...(restored?.integrity ? { expected: restored.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);
@@ -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
- if (!current) return; // daemon removes its manifest only after owned children stop
198
- if (current.instanceId !== instanceId) throw new Error("a new hub instance started; it was not stopped");
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");