@staix/agent-hub 0.12.1 → 0.12.2

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,10 @@
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.12.2
6
+
7
+ - An edit approved after another peer changed the file now applies its fragment replacement to current contents. It rechecks that the old fragment still matches exactly once and refuses a stale match. Both write and edit revalidate their paths after approval, so a path replaced with an escaping symlink during the wait is refused (#98).
8
+
5
9
  ## 0.12.1
6
10
 
7
11
  - Exhausted task deliveries escalate with the last error and notify the console. Three consecutive exhausted deliveries exclude a peer from routing until a delivery completes. A local worker validates its gateway, model inventory and a minimal availability call before attaching; doctor flags an unserved fixed model (#89).
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.12.1, control protocol 11. Durable delivery records distinguish queued
7
+ Status: 0.12.2, control protocol 11. 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
 
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.12.1 and control protocol 11. Live verification
3
+ This guide describes ahub 0.12.2 and control protocol 11. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Install and start
@@ -481,8 +481,8 @@ source and carries every recovery fix released up to it. Protocol 8 and older
481
481
  project directory, without replacing the global CLI first:
482
482
 
483
483
  ```bash
484
- bunx --package @staix/agent-hub@0.12.1 ahub upgrade --to 0.12.1 --dry-run
485
- bunx --package @staix/agent-hub@0.12.1 ahub upgrade --to 0.12.1 --yes
484
+ bunx --package @staix/agent-hub@0.12.2 ahub upgrade --to 0.12.2 --dry-run
485
+ bunx --package @staix/agent-hub@0.12.2 ahub upgrade --to 0.12.2 --yes
486
486
  ```
487
487
 
488
488
  | Running now | Coordinator to use |
@@ -520,14 +520,14 @@ projects first:
520
520
 
521
521
  ```bash
522
522
  ahub restart --dry-run
523
- ahub upgrade --to 0.12.1 --dry-run
523
+ ahub upgrade --to 0.12.2 --dry-run
524
524
  ```
525
525
 
526
526
  Apply only after reviewing the plan:
527
527
 
528
528
  ```bash
529
529
  ahub restart --yes
530
- ahub upgrade --to 0.12.1 --yes
530
+ ahub upgrade --to 0.12.2 --yes
531
531
  ahub recovery status <operation-id>
532
532
  ahub recovery resume <operation-id>
533
533
  ahub recovery abort <operation-id>
package/docs/smoke.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  `scripts/check.sh` covers everything against fakes. The legs below need real accounts and an interactive terminal, so they are run by hand and recorded here.
4
4
 
5
+ ## Approval race live reproduction and candidate verification (#98)
6
+
7
+ Measured on 2026-10-02 KST with installed 0.12.1 and the correction candidate,
8
+ using real Kimi 2.1.1 and Pi 0.86.0 in disposable git projects:
9
+
10
+ - Pi requested an edit of only `PI_MARKER`, and its allow-once approval waited.
11
+ Kimi changed the separate `KIMI_MARKER` line. Approving Pi restored the old
12
+ Kimi value (`pending`) while retaining `pi-live`. The hub emitted one concurrent
13
+ conflict event with both turn ids, so conflict detection worked while the tool
14
+ still overwrote unrelated current contents.
15
+ - The candidate repeated the same real-agent interleaving and kept both
16
+ `kimi-live` and `pi-live`. One concurrent conflict was recorded, and Kimi's
17
+ reviewer role produced an actual `pi:accepted -> pi:done -> kimi:approved`
18
+ task history. This is the runtime fix included in 0.12.2.
19
+ - Regression checks cover a fragment changed while approval waits and write/edit
20
+ paths changed into out-of-project symlinks during approval. Existing approval
21
+ denial and initial exact-match checks remain intact.
22
+ - The same live acceptance session used real Codex 0.159.3 (reply `pong`, no hub
23
+ negative RPC ids leaked), the real local worker, and real Switchyard routes.
24
+ Controlled gateway 503s verified escalation, three-exhausted-delivery routing
25
+ exclusion, recovery after a completed delivery, and a post-write needs-review
26
+ hold with one notice and explicit resolution. Idle model/route replacements and
27
+ busy refusal were observed; doctor flagged an unserved fixed model.
28
+ - Pi native session usage independently totalled 36,390 tokens, exactly matching
29
+ hub events. A candidate session's complete native and hub totals also matched
30
+ at 100,940 tokens. Real Kimi submitted a 1,430-character checkpoint summary and
31
+ handed open work to Pi after a controlled 95% reading.
32
+ - The real Codex weekly reading was 2%, so natural near-limit pause remains
33
+ unverified (issue #1). The injected 95% empty-work check spent zero extra
34
+ checkpoint turns; it is separate evidence from the natural provider leg.
35
+
36
+ The gateway outages, response barriers and manual quota readings were controlled
37
+ fault inputs. Native/model replies were verified against files, task history,
38
+ events and native session usage. Disposable test hubs were stopped and removed
39
+ from the registry; credentials and raw conversations remain outside the ledger.
40
+
5
41
  ## Reliability and self-development run (#89-#95, 0.12.1)
6
42
 
7
43
  Measured on 2026-10-01 in an isolated worktree of this repository:
@@ -1141,3 +1141,13 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1141
1141
  - Pi assistant usage is forwarded through the authenticated bridge and recorded before settlement, without counting the same message both at `message_end` and `agent_end`.
1142
1142
  - An idle peer with no open owner/reviewer task and no queued or active delivery skips the quota checkpoint turn; the pause proceeds immediately and the log records the skip.
1143
1143
  - Control protocol 11 adds queue hold metadata; the current coordinator supports authenticated protocol 9, 10 and 11 recovery sources.
1144
+
1145
+
1146
+ ## Approval-time file validation, 0.12.2 (#98)
1147
+
1148
+ The hub-native write and edit tools validate paths before requesting approval and
1149
+ again after approval, at mutation time. Edit retains its early exact-fragment
1150
+ check, then reads current contents after approval and requires the old fragment
1151
+ to occur exactly once. The approved replacement applies to those current bytes,
1152
+ so another peer's unrelated edits made during the wait are retained. A changed
1153
+ fragment or an escaping path returns an error without a write.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
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.12.1",
3
+ "version": "0.12.2",
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.12.1",
15730
+ version: "0.12.2",
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",
@@ -116,19 +116,25 @@ export async function runTool(name: string, rawArgs: string, ctx: ToolContext):
116
116
  return out.length > OUTPUT_CAP ? `${out.slice(0, OUTPUT_CAP)}\n(truncated: read again with offset/limit)` : out;
117
117
  }
118
118
  case "write": {
119
- const file = guardPath(ctx, String(a.path), "write");
119
+ let file = guardPath(ctx, String(a.path), "write");
120
120
  const content = String(a.content ?? "");
121
121
  if (!(await ctx.permit(`write ${a.path} (${content.length} chars):\n${preview(content)}`))) return "error: the user did not approve this write";
122
+ file = guardPath(ctx, String(a.path), "write");
122
123
  mkdirSync(dirname(file), { recursive: true });
123
124
  writeFileSync(file, content);
124
125
  return `wrote ${a.path}`;
125
126
  }
126
127
  case "edit": {
127
- const file = guardPath(ctx, String(a.path), "write");
128
- const text = readFileSync(file, "utf8");
129
- const count = text.split(String(a.old)).length - 1;
128
+ let file = guardPath(ctx, String(a.path), "write");
129
+ let text = readFileSync(file, "utf8");
130
+ let count = text.split(String(a.old)).length - 1;
130
131
  if (!a.old || count !== 1) return `error: \`old\` must match exactly once, it matched ${count} times`;
131
132
  if (!(await ctx.permit(`edit ${a.path}:\n- ${preview(String(a.old), 600)}\n+ ${preview(String(a.new ?? ""), 600)}`))) return "error: the user did not approve this edit";
133
+ // Approval can outlive another peer's edit or a path change. Apply only the approved fragment to current bytes.
134
+ file = guardPath(ctx, String(a.path), "write");
135
+ text = readFileSync(file, "utf8");
136
+ count = text.split(String(a.old)).length - 1;
137
+ if (count !== 1) return `error: file changed during approval; \`old\` must match exactly once, it matched ${count} times`;
132
138
  writeFileSync(file, text.replace(String(a.old), () => String(a.new ?? "")));
133
139
  return `edited ${a.path}`;
134
140
  }