@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 +4 -0
- package/README.md +1 -1
- package/docs/operations.md +5 -5
- package/docs/smoke.md +36 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +10 -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/local/tools.ts +10 -4
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.
|
|
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
|
|
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.12.
|
|
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.
|
|
485
|
-
bunx --package @staix/agent-hub@0.12.
|
|
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.
|
|
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.
|
|
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": "agent-hub",
|
|
3
|
-
"version": "0.12.
|
|
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.
|
|
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",
|
package/src/local/tools.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
}
|