@staix/agent-hub 0.12.40 → 0.12.42

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,21 @@
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.42
6
+
7
+ `ahub agy` starts again after the Antigravity CLI updated itself (#589); the rest is contributor tooling (#591, #593, #595).
8
+
9
+ - `ahub agy` accepts the Antigravity CLI 1.4.x line instead of exactly 1.4.2: 1.4.2, the T0-verified version, launches as before, another 1.4.x patch launches with one stderr warning naming its version, and any other version is still refused before a hub connection or plugin write. agy updates itself, and 0.12.41 refused 1.4.3 (#589).
10
+ - `scripts/build.mjs` builds the plugin bundle with the Bun named in `.bun-version`: when the running Bun differs it re-runs itself once through `bun x bun@<pinned>`. A self-updated local Bun made the bundle check call a clean `main` stale and its remedy rebuild with the wrong Bun (#591).
11
+ - The test gate's whole-suite watchdog cut off passing runs: hosted CI now gives macOS 1260 s and Linux 1080 s inside a 25-minute job, and the local default is 960 s; the 20 s per-test timeout is unchanged (#593).
12
+ - `test/terminal-recovery.test.ts` no longer records an ambient `CLAUDE_CONFIG_DIR` from the shell running the tests (#595).
13
+
14
+ ## 0.12.41
15
+
16
+ A one-fix release for a 0.12.40 regression.
17
+
18
+ - `ahub codex` starts in a recorded Orca terminal again: 0.12.40 put the terminal launch id into the `--remote` URL (`ws://127.0.0.1:<port>/?launchId=...`) and Codex refused it with `invalid remote address`, because it accepts only `ws://host:port`. The launcher now passes `--remote-auth-token-env AGENTHUB_LAUNCH_ID`, Codex sends the id as the upgrade's bearer token, and the proxy reads it from there; `--remote-auth-token-env` joins the hub-owned Codex flags (#586).
19
+
5
20
  ## 0.12.40
6
21
 
7
22
  A bug-fix release. Held task offers settle on their own (#434), terminal recovery binds the live native owner (#482), a busy jcode takes an important message into its running turn (#572), and the check runner, hop-capped sends and planner pulls say what happened instead of hanging or claiming success (#558, #550, #531).
package/README.md CHANGED
@@ -5,7 +5,7 @@ Codex, Kimi Code, Pi and a hub-owned local worker (Qwen Code, Jcode and Antigrav
5
5
  optional) share a message bus and a task board per project and one memory on the
6
6
  machine, each attached through its own native interface.
7
7
 
8
- Status: 0.12.40, control protocol 16. Bun + TypeScript, one loopback daemon per
8
+ Status: 0.12.42, control protocol 16. Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
11
  ## What it does today
@@ -84,14 +84,17 @@ writes the running hub's own plugin bundle (#502). Since 0.12.37 the hub accepts
84
84
  Since 0.12.39 `ahub jcode` opens Jcode's native TUI on the hub-owned ACP session by
85
85
  default (#464); `--mode headless` or `ahub settings set peers.jcode.start_mode headless`
86
86
  keeps headless startup. On 0.12.39 the TUI launcher stopped after the first hub turn;
87
- 0.12.40 opens it again (#564).
87
+ 0.12.40 opens it again (#564). On 0.12.40 `ahub codex` in a recorded Orca terminal
88
+ failed with `invalid remote address`; 0.12.41 starts it again (#586). Since 0.12.42
89
+ `ahub agy` accepts the Antigravity CLI 1.4.x line (1.4.2 verified; other patches warn),
90
+ because agy updates itself and 0.12.41 refused 1.4.3 (#589).
88
91
  Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
89
92
  refuse while cancelled rows exist. Live-test delegated CLI commands need a daemon
90
93
  advertising `cli_audit_delegated` and refuse with an upgrade remedy otherwise
91
94
  (#410). For local/Pi implementation, opt in to an available
92
95
  `checks.runner = "container"`; issue-backed owners also need authenticated
93
96
  spec access. PII work and designated reviewers retain their separate exemptions.
94
- See [release verification](docs/verification/2026-10-20-0.12.40.md) for the live legs
97
+ See [release verification](docs/verification/2026-10-20-0.12.42.md) for the live legs
95
98
  still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
96
99
  real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
97
100
  (#478), a live headless Claude review (#223), a Claude session over 32 MiB (#517), a
@@ -70,7 +70,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
70
70
  - Report reviewer failures (`onUnavailable`: `start_failed`, `quota`, `no_verdict`) only after the child's group is confirmed gone and only while its generation is current: a person's stop owns its hold and never reroutes. `quota` comes only from the backend's structured `failure(event)`, never from vendor text. An NDJSON frame over 256 KiB or more than 4 MiB of output aborts the review as `needs_review`.
71
71
  - Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only the pinned `codex-cli` version; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`.
72
72
 
73
- - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` other than the pinned 1.4.2, and a hub without the `native_hook_launch` feature.
73
+ - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` outside the 1.4.x line (1.4.2 is T0-verified; other 1.4.x patches launch with a stderr warning, #589), and a hub without the `native_hook_launch` feature.
74
74
  - Only the person's `ahub agy` launcher manages `.agents/plugins/agent-hub/{plugin.json,mcp_config.json,hooks.json}`. Refuse linked/foreign entries before any write; cleanup only the matching launch. Never write project-level `.agents/hooks.json` or personal Gemini settings. The plugin MCP server is named `agent-hub_agent-hub` by the pinned native client.
75
75
  - Managed native facts use the definition's typed `NativeFactsPort` and the daemon's ephemeral `native_hook_launch` authority. Bind every observation to the daemon, launch, native session, project and current socket generation. Stop requires `fullyIdle`, normal termination and a fresh validated completion row; a missing readback keeps the peer busy. Native fact acknowledgement accepts only SDK ephemeral-message rows in the exact session transcript path.
76
- - `terminalOwner(launchId)` (`src/hub/peers.ts`, #482) answers only for the adapter's current native TUI owner bound to that launch (Codex: the claimed link with that `launchId`, both sockets open; Pi: a started, verified TUI owner on its requested session); a start acknowledgement is never ownership. A Codex link that is not the claimed one or has a closed socket never adopts a thread.
76
+ - `terminalOwner(launchId)` (`src/hub/peers.ts`, #482) answers only for the adapter's current native TUI owner bound to that launch (Codex: the claimed link with that `launchId`, both sockets open; the launcher sends the id as the upgrade's bearer token through `--remote-auth-token-env`, because `codex --remote` refuses any path or query (#586); Pi: a started, verified TUI owner on its requested session); a start acknowledgement is never ownership. A Codex link that is not the claimed one or has a closed socket never adopts a thread.
@@ -16,7 +16,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
16
16
  - Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
17
17
 
18
18
  - `scripts/check.sh` holds the repo's heavy-job slot (`<git common dir>/ahub-heavy.lock`, #398) for the whole run, so a test never runs the real gate; `test/check-lock.test.ts` runs a copy in a scratch repo.
19
- - The whole-suite watchdog is `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted values for both OSes in `.github/workflows/check.yml`; #378, macOS CI hit it while tests still passed): raise it only when the suite was still progressing at the cutoff, never for a hung test, and keep the per-test 20 s timeout. The check job's `timeout-minutes` must leave room above that value for setup: raise them together.
19
+ - The whole-suite watchdog is `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted values for both OSes in `.github/workflows/check.yml`; #378 and #593, macOS CI hit it while tests still passed; currently 960 s local, 1080 s Linux, 1260 s macOS): raise it only when the suite was still progressing at the cutoff, never for a hung test, and keep the per-test 20 s timeout. The check job's `timeout-minutes` must leave room above that value for setup: raise them together.
20
20
  - A seed's named test must run on Linux, where the seeded job runs: a darwin-only test reports no result there and fails the job (#382). Cover a macOS-only protection with its regression test instead.
21
21
 
22
22
  - Keep CI's Bun version sourced from `.bun-version` through the pinned setup-bun action; update the version file rather than adding per-job version literals.
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.12.40 and control protocol 16. Live verification
3
+ This guide describes ahub 0.12.42 and control protocol 16. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Agent-operated live verification
@@ -2044,21 +2044,21 @@ section is what they run.
2044
2044
 
2045
2045
  Upgrade running projects with the target release's own coordinator. It accepts
2046
2046
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
2047
- 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.40), and only
2047
+ 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.42), and only
2048
2048
  a target on its own protocol, so the target's coordinator fits every supported
2049
2049
  source and carries every recovery fix released up to it. Protocol 8 and older
2050
2050
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
2051
2051
  project directory, without replacing the global CLI first:
2052
2052
 
2053
2053
  ```bash
2054
- bunx --package @staix/agent-hub@0.12.40 ahub upgrade --to 0.12.40 --dry-run
2055
- bunx --package @staix/agent-hub@0.12.40 ahub upgrade --to 0.12.40 --yes
2054
+ bunx --package @staix/agent-hub@0.12.42 ahub upgrade --to 0.12.42 --dry-run
2055
+ bunx --package @staix/agent-hub@0.12.42 ahub upgrade --to 0.12.42 --yes
2056
2056
  ```
2057
2057
 
2058
2058
  | Running now | Coordinator to use |
2059
2059
  | --- | --- |
2060
2060
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
2061
- | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.40 (protocol 16) | the target's, through `bunx` as above |
2061
+ | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.42 (protocol 16) | the target's, through `bunx` as above |
2062
2062
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
2063
2063
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
2064
2064
 
@@ -2090,14 +2090,14 @@ projects first:
2090
2090
 
2091
2091
  ```bash
2092
2092
  ahub restart --dry-run
2093
- ahub upgrade --to 0.12.40 --dry-run
2093
+ ahub upgrade --to 0.12.42 --dry-run
2094
2094
  ```
2095
2095
 
2096
2096
  Apply only after reviewing the plan:
2097
2097
 
2098
2098
  ```bash
2099
2099
  ahub restart --yes
2100
- ahub upgrade --to 0.12.40 --yes
2100
+ ahub upgrade --to 0.12.42 --yes
2101
2101
  ahub recovery status [<operation-id>]
2102
2102
  ahub recovery resume [<operation-id>]
2103
2103
  ahub recovery abort [<operation-id>]
@@ -2946,8 +2946,8 @@ unreadable result runs the main gate again and refuses release. A manual
2946
2946
  it is never accepted as full-gate evidence. A `timing_runs=20` dispatch runs only the daemon, PII screen and ACP
2947
2947
  test files twenty times on macOS, in its own concurrency group; it does not replace the full gate.
2948
2948
  `scripts/check.sh` stops a suite still running after `AHUB_CHECK_HANG_S` seconds
2949
- (600 by default); hosted macOS CI sets 780 inside the unchanged 15-minute job
2950
- limit (#378).
2949
+ (960 by default); hosted CI sets 1080 on Linux and 1260 on macOS inside a 25-minute
2950
+ job limit (#378, #593).
2951
2951
  The gate holds `<git common dir>/ahub-heavy.lock` across every worktree of a
2952
2952
  repository. A second gate waits up to `AHUB_CHECK_LOCK_WAIT_S` (1800 seconds by
2953
2953
  default; 0 fails immediately) and names the holder. A holder whose PID is gone
@@ -3235,7 +3235,7 @@ Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or
3235
3235
 
3236
3236
  ## Optional Antigravity (agy) peer
3237
3237
 
3238
- Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI 1.4.2 as a fresh pull-only TUI in that terminal and refuses any other version. The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and extra agy instances are unavailable. Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
3238
+ Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2 is the verified version, any other 1.4.x launches with a stderr warning (agy updates itself), and other versions are refused (#589). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and extra agy instances are unavailable. Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
3239
3239
 
3240
3240
  The launcher writes exactly `plugin.json`, `mcp_config.json` and `hooks.json` in `.agents/plugins/agent-hub/`, refusing linked or foreign entries before any write, and removes only its own matching files when the TUI ends. It never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Run `ahub init` afterwards to ignore the untracked plugin directory; a tracked plugin is written with a warning. The native model sees the hub tools under the server name `agent-hub_agent-hub`.
3241
3241
 
@@ -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.12.40
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.42
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -30,9 +30,17 @@ and upgrade or crash recovery, see the [operations guide](operations.md). To
30
30
  move running projects to a newer release later, run `ahub upgrade` in a
31
31
  terminal: it reviews the plan with you, applies it and follows it.
32
32
 
33
+ ## Upgrading to 0.12.42
34
+
35
+ 0.12.42 lets `ahub agy` start again after the Antigravity CLI updated itself to 1.4.3: the launcher accepts the 1.4.x line, launching 1.4.2 (the verified version) silently and another 1.4.x patch with a warning (#589). The rest of the release is contributor tooling (#591, #593, #595). Coming from 0.12.40 or older, the notes below apply too.
36
+
37
+ ## Upgrading to 0.12.41
38
+
39
+ 0.12.41 fixes one 0.12.40 regression: `ahub codex` in a recorded Orca terminal stopped at `invalid remote address`, because the launcher put the terminal launch id into the `--remote` URL, which Codex accepts only as `ws://host:port`. The id now travels as the upgrade's bearer token (#586). Coming from 0.12.39 or older, the 0.12.40 notes below apply too.
40
+
33
41
  ## Upgrading to 0.12.40
34
42
 
35
- Restart Claude sessions after upgrading: each keeps the plugin server it started with until restarted, so a restart loads the 0.12.40 plugin. That plugin asks Claude Code to list the hub tools again when a hub reconnect changes them (#490), and one from an older `ahub claude` launch is refused and stops reconnecting instead of reclaiming the peer (#551).
43
+ Restart Claude sessions after upgrading: each keeps the plugin server it started with until restarted, so a restart loads the 0.12.42 plugin. That plugin asks Claude Code to list the hub tools again when a hub reconnect changes them (#490), and one from an older `ahub claude` launch is refused and stops reconnecting instead of reclaiming the peer (#551).
36
44
 
37
45
  `ahub jcode` opens its TUI again; on 0.12.39 it stopped after the first hub turn (#564). A busy Jcode now takes an `important` message into its running turn (#572), and `ahub doctor` lists the extra MCP servers the hub-owned Jcode loads from its config files. See [Jcode operations](operations.md#optional-jcode-peer).
38
46
 
@@ -54,7 +62,7 @@ Upgrading from 0.12.37 or earlier, the 0.12.38 notes below also apply.
54
62
 
55
63
  The 0.12.38 plugin reports a reply stopped by the hop cap as not delivered instead of as sent (#546); the restart above loads it.
56
64
 
57
- Antigravity (`agy`) can join as an optional pull-only implementer (#485). It is off roster by default: install the Antigravity CLI 1.4.2, run `ahub roster add agy`, then run `ahub agy` in a terminal. Its native permissions are unmanaged and it has no hub sandbox. See [Antigravity operations](operations.md#optional-antigravity-agy-peer).
65
+ Antigravity (`agy`) can join as an optional pull-only implementer (#485). It is off roster by default: install the Antigravity CLI 1.4.x (1.4.2 verified; other 1.4.x patches launch with a warning), run `ahub roster add agy`, then run `ahub agy` in a terminal. Its native permissions are unmanaged and it has no hub sandbox. See [Antigravity operations](operations.md#optional-antigravity-agy-peer).
58
66
 
59
67
  Reviews can rotate across fresh Claude, Codex and Kimi reviewer sessions (#519, #533). Declare headless reviewer-only instances in `.agenthub/config.local.json`; Codex and Kimi join only with `review_mode: "fresh"`. Codex also needs an installed Codex 0.146.0 (select it with `codex_bin` when PATH resolves another version), and Kimi needs Kimi Code 2.2.0 on PATH, signed in with its own OAuth login. A headless reviewer instance without `review_mode` keeps its native behavior and stays out of the pool. agy is not a reviewer: naming it in `review.pool` shows an unavailable skip in `ahub route explain`, never a review. Once a train is active or frozen, or `review.pool` is set or fresh reviewer instances are declared, `ahub upgrade` refuses a target release without that support, so an older release cannot take the project over. See [fresh reviewer pool](operations.md#fresh-reviewer-pool-519).
60
68
 
package/docs/security.md CHANGED
@@ -5,7 +5,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
5
5
  ## Trust boundaries
6
6
 
7
7
  - **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
8
- - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. External clients cannot claim the console user's id or a hub-managed peer's id.
8
+ - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. The bearer header a Codex TUI sends to the proxy carries only the terminal launch id (#586): it binds terminal ownership and is not a credential. External clients cannot claim the console user's id or a hub-managed peer's id.
9
9
  - **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default.
10
10
  - **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`, `instances`) apply only from a config file git confirms nobody committed (`instances` is read only from an untracked `.agenthub/config.local.json`; in `config.json` it is ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot has a 10 s cap). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
11
11
  - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. It stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
@@ -254,7 +254,7 @@ this production table. Startup diagnostics withhold native host/payload details.
254
254
 
255
255
  Jcode is optional, source/version-fenced and runs native tools without a hub sandbox or hub permission-mode mapping. .jcode joins the scoped-grant exclusion, including lexical/resolved aliases. Only a person writes its unique project MCP entry; foreign/override/symlink entries refuse. The hub owns a private daemon/socket and ACP group, never the global daemon, and copies no credentials. It accepts only a plain jcode release from 0.98.0 up to, not including, 1.0.0 (#529), and runs every owned jcode process (probe, serve, ACP and TUI) with `JCODE_NO_TELEMETRY=1`, because jcode's telemetry can report the provider account's email. Native diagnostics withhold payloads/hosts. Unproved conversation loads refuse; no fresh replacement is called a restore. A finished task's advisory about the project root checkout names the branch only after the hub's whole public-text screen (`Tasks.publicMetadataText`: a PII pattern, a secret shape and a project-private path) passes it and it is a plain ref name; otherwise a generic sentence carries no part of it (#572). Native TUI launch exposes its private socket/session tuple only to the authenticated console, bound to the same live owned socket and session after read-only native persistence proof. Tools/status do not receive it. The TUI has no single-writer guard: person input may interleave with ACP. Native policy remains unmanaged and closing the frontend does not stop ACP. An `important` message reaches a busy Jcode as a soft interrupt written to the hub's own private server socket for the owned session (#572); the hub never cancels a turn to deliver, and a steer that was sent but not acknowledged, or whose turn ended first, is held as `needs_review`, never replayed. Jcode has no switch to choose its MCP servers, so the owned serve and ACP processes load every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, besides the hub's own; `ahub doctor` lists their names only. Point `jcode_home` at a separate home, or remove a server from those files, to keep it out.
256
256
 
257
- Antigravity (`agy`, #485) is optional, cloud and off roster by default. It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused. Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
257
+ Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2 has a native receipt, so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused. Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
258
258
 
259
259
  # Transient ACP activity (#286)
260
260
 
package/docs/smoke.md CHANGED
@@ -1966,7 +1966,7 @@ Kimi usage ceiling: 2.2.0 print stream-json exposes no token counts. Keep unobse
1966
1966
 
1967
1967
  ## Antigravity P1 (#485)
1968
1968
 
1969
- - Pin agy 1.4.2; verify its executable/version and keep the actual TUI/IDE leg separate from the account-free [T0 transport receipt](verification/2026-10-19-agy-p1-t0.md).
1969
+ - Run agy on the 1.4.x line (1.4.2 verified; other patches warn, #589); verify its executable/version and keep the actual TUI/IDE leg separate from the account-free [T0 transport receipt](verification/2026-10-19-agy-p1-t0.md).
1970
1970
  - With a reviewed compatible daemon running and agy explicitly on roster, the person runs `ahub agy` in a terminal. Confirm peer `agy` is pull-only, native permissions unmanaged and no hub sandbox/budget source is claimed.
1971
1971
  - In the native TUI call `hub_inbox`, `hub_send`, and a permitted board read/proposal within a non-PII task. Confirm the tool audience and actor remain `agy`, the native model consumes the tool results, and no unsolicited push or PII task text reaches it.
1972
1972
  - End the TUI normally. Confirm its group/socket detaches and only its matching managed three-file plugin is cleaned up; unrelated project/global settings remain unchanged.
@@ -3881,3 +3881,5 @@ The pinned agy 1.4.2 T0 receipt precedes implementation: isolated HOME/`.gemini`
3881
3881
  The P1 host introduces additive `native_hook_launch` and `native_facts` control requests. Only the person begins/ends a launch; hook tools cannot grant themselves launcher authority, and conductor starts are refused. The interactive child remains in the launcher's foreground process group. Launch authority belongs to the console socket that began it and is revoked on that socket's close; an old socket cannot revoke a replacement launch. The managed launcher embeds its daemon/launch identity in the plugin hooks, and the host binds observations to one native conversation and socket generation. It acknowledges a normal fully-idle Stop only after a fresh native completion row. Turn-free facts are injected at the observed model boundary and acknowledged only from SDK transcript rows; unobserved native file effects receive no attribution. Hook input over one MiB is refused before decoding or forwarding. Out-of-ceiling agy roles are dropped with a config log notice before conductor validation, preserving other peers' admission.
3882
3882
 
3883
3883
  The collected T0 proves the plugin, hook codec and read transport, not a native prompt override. P1 therefore keeps all runtime modes unmanaged and refuses unattended/headless/resume selectors until those individual mappings are proved. The old daemon refuses the additive launch request rather than silently weakening hook identity. An actual project TUI launch requires a running daemon with this feature; source/fixture verification does not claim that live rollout.
3884
+
3885
+ Version line (#589, owner decision 2026-10-20): agy updates itself, so the launcher accepts the 1.4.x line instead of exactly 1.4.2. 1.4.2 launches silently; another 1.4.x patch launches with one stderr warning naming the observed version and the 1.4.2 receipt; any other version, unparseable output or a failing `--version` is refused before a hub connection or plugin write. A T0 re-run on a newer patch is a separate follow-up.
@@ -0,0 +1,44 @@
1
+ # 0.12.41 release verification
2
+
3
+ This record covers release preparation and source review. It does not claim publication, installation, a running-hub restart or native/live acceptance beyond the manual Codex check below.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.12.41, control protocol 16 (unchanged; no daemon feature added), from merged main. The person reported the 0.12.40 failure `invalid remote address ws://127.0.0.1:4602/?launchId=...` from an Orca launch and directed on 2026-10-20 "이슈 586만 구현하고 배포하라" (implement and ship only #586).
8
+
9
+ - Codex launch (#586, PR #587): the terminal launch id moved from the `--remote` URL query to `--remote-auth-token-env AGENTHUB_LAUNCH_ID`; the proxy reads `Authorization: Bearer <id>`; `--remote-auth-token-env` is a hub-owned Codex flag.
10
+ - Still open (unchanged from 0.12.40): every live leg listed in [the 0.12.40 record](2026-10-20-0.12.40.md), including a real upgrade that exercises the `terminal_owner` record (#482).
11
+ - Not in this release: everything else on the board; the person limited the release to #586.
12
+
13
+ ## Reviews
14
+
15
+ | PR | Final head | Review | CI |
16
+ | --- | --- | --- | --- |
17
+ | #587 (#586) | 6c22808a | OCR delegation review of the source diff: no findings. codex-connector: P1 (the #482 leaf-wiring fixture still asserted the query) fixed at 6c22808a; P2 (negotiate bearer transport with a running 0.12.40 daemon) answered and declined: a 0.12.40 launcher's query makes Codex refuse to start, so a new daemon keeping query parsing serves no client, and a new CLI against an old daemon starts Codex and lacks only the terminal-owner proof until `ahub upgrade` restarts the daemon | exact head, success (both OS checks, 4/4 seeded shards) |
18
+
19
+ ## Manual check
20
+
21
+ codex-cli 0.163.0, isolated `HOME`/`CODEX_HOME`, driven in a pty: `codex --remote ws://127.0.0.1:<p> --remote-auth-token-env X` sent `Authorization: Bearer <value of X>` on the loopback WebSocket upgrade. The same Codex refuses `--remote` with any path or query.
22
+
23
+ ## Document review
24
+
25
+ Nine documents were stale against the 0.12.40 stamps (`e57b1936`), all through #587 and this release commit. Reviewed against `710c0771` and the release commit `644f9a45`.
26
+
27
+ | Document | Result | Checked claims and correction |
28
+ | --- | --- | --- |
29
+ | `README.md` | corrected | Status 0.12.41; the 0.12.40 Codex launch failure and its fix (#586); verification link. |
30
+ | `docs/quickstart.md` | corrected | Install pin `v0.12.41`; "Upgrading to 0.12.41" names the regression and points 0.12.39 users to the 0.12.40 notes; plugin restart names 0.12.41. |
31
+ | `docs/operations.md` | corrected | Version line, protocol 16 range and the upgrade commands name 0.12.41. |
32
+ | `docs/security.md` | corrected | The Codex proxy's bearer header carries only the launch id and is not a credential (#586). |
33
+ | `docs/agent-notes/adapters.md` | corrected in #587 | `terminalOwner(launchId)`: the id arrives as the bearer token because `codex --remote` refuses a path or query. |
34
+ | `docs/agent-notes/daemon.md` | no change | No daemon, recovery or control rule changed; the launch plan change is covered in `adapters.md`. |
35
+ | `docs/agent-notes/tasks.md` | no change | No task or tool rule changed. |
36
+ | `docs/agent-notes/tests.md` | no change | A fixture assertion changed; no test-gate rule changed. |
37
+ | `src/cli/help.ts` | no change | No command, subcommand or flag changed; `--remote-auth-token-env` is refused as hub-owned, like `--remote`. |
38
+
39
+ ## Checks
40
+
41
+ - PR #587's exact-head CI passed (table above). A local `scripts/check.sh` on the PR head was stopped by the whole-suite watchdog at 600 s under host load (load average 5.7); its only failures before the stop were the fixture fixed at 6c22808a and a `CLAUDE_CONFIG_DIR` leak from the agent session's Orca profile into `test/terminal-recovery.test.ts`, which passes with that variable unset. CI is the full gate for this release.
42
+ - The release branch is merged main plus the version, release notes, bundle and document corrections. The bundle is rebuilt with the pinned Bun 1.4.2 (`scripts/build.mjs`) after `bun install --frozen-lockfile`; main's source reproduces the committed bundle byte-for-byte with that toolchain.
43
+ - The stale stamps are restamped to the release commit `644f9a45`; `node scripts/check-docs.mjs --require-fresh` result is recorded in the release PR.
44
+ - Exact-head CI on the release PR.
@@ -0,0 +1,48 @@
1
+ # 0.12.42 release verification
2
+
3
+ This record covers release preparation and source review. It does not claim publication, installation, a running-hub restart or a live `ahub agy` TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.12.42, control protocol 16 (unchanged; no daemon feature added), from merged main. The local Antigravity CLI updated itself to 1.4.3 on 2026-10-20 and `ahub agy` refused it (`agy P1 requires the verified Antigravity CLI 1.4.2`). The person directed on 2026-10-20 to relax the gate to the 1.4.x line with a warning, register and fix the Bun drift and the problems found on the way, merge, and ship.
8
+
9
+ - agy version line (#589, PR #590): 1.4.2 launches silently, another 1.4.x patch launches with one stderr warning, anything else is refused before a hub connection or plugin write.
10
+ - Pinned Bun for the bundle (#591, PR #592): `scripts/build.mjs` re-runs itself through `bun x bun@<.bun-version>` when the running Bun differs.
11
+ - Test watchdog budget (#593, PRs #594 and #598): hosted macOS 1260 s and Linux 1080 s inside a 25-minute job, local default 960 s; the 20 s per-test timeout unchanged. The first release PR run hit the old 780 s Linux cutoff one second after a passing test (run 39441258200), which led to #598.
12
+ - Test isolation (#595, PR #596): the recorded-launch allowlist test clears the ambient `CLAUDE_CONFIG_DIR`.
13
+ - Still open: a T0 re-run on agy 1.4.3 (its changelog changes `PostToolUse` failure handling and `agy plugin validate`, not the hook envelope the hub parses), and every live leg listed in [the 0.12.40 record](2026-10-20-0.12.40.md).
14
+
15
+ ## Reviews
16
+
17
+ | PR | Final head | Review | CI |
18
+ | --- | --- | --- | --- |
19
+ | #590 (#589) | c8837f8f (reviewed diff 6a93e48f plus a merge of main) | OCR delegation review: no High or Medium findings. codex-connector: no findings | exact head, success (both OS checks) |
20
+ | #592 (#591) | d1ac9033 | OCR delegation review: no findings. codex-connector: no findings | exact head, success (both OS checks, 4/4 seeded shards) |
21
+ | #594 (#593) | 16ee60db | OCR delegation review: no findings (no other job runs `check.sh`; 25 minutes covers 1260 s plus setup). codex-connector: no findings | exact head, success (both OS checks) |
22
+ | #598 (#593) | b023cd6b | Config-only diff reviewed: 1080 s plus setup fits the 25-minute job. codex-connector: no findings | exact head, success (both OS checks) |
23
+ | #596 (#595) | 7adf8c45 (bf7c66b7 plus a merge of main) | Test-only diff reviewed (excluded from OCR by path). codex-connector: no findings | exact head, success (both OS checks) |
24
+
25
+ Claude and Copilot PR reviews were skipped for quota on all five PRs.
26
+
27
+ ## Document review
28
+
29
+ Seven documents were stale against the 0.12.41 stamps (`644f9a45`), through #590, #594 and this release. Restamped to the release branch head `6f012880`; `docs/agent-notes/tests.md` and `docs/operations.md` again to `ba26148f` after #598 merged.
30
+
31
+ | Document | Result | Checked claims and correction |
32
+ | --- | --- | --- |
33
+ | `README.md` | corrected | Status 0.12.42; the agy 1.4.x line (#589); verification link. |
34
+ | `docs/quickstart.md` | corrected | Install pin `v0.12.42`; "Upgrading to 0.12.42"; plugin restart names 0.12.42; agy install names the 1.4.x line (#590). |
35
+ | `docs/operations.md` | corrected | Version line, protocol 16 range and upgrade commands name 0.12.42; agy 1.4.x line (#590); watchdog values (#594, #598). |
36
+ | `docs/security.md` | corrected | The agy launcher's 1.4.x acceptance, the unverified-patch warning and the refusal boundary (#589). |
37
+ | `docs/agent-notes/adapters.md` | corrected in #590 | The launcher refuses versions outside the 1.4.x line. |
38
+ | `docs/agent-notes/tests.md` | corrected in #594 and #598 | Watchdog values per platform (Linux 1080 s). #596 changed a test only; no test-gate rule changed. |
39
+ | `src/cli/help.ts` | corrected in #590 | `ahub agy` names the 1.4.x line; no command, subcommand or flag changed. |
40
+
41
+ ## Checks
42
+
43
+ - Each PR's exact-head CI passed (table above); all checks share one serialized queue.
44
+ - Local full `scripts/check.sh` runs before #593/#595 were cut off by the 600 s watchdog while tests still passed, and showed the `CLAUDE_CONFIG_DIR` failure fixed by #595. CI is the full gate for this release.
45
+ - The bundle was rebuilt with `bun run build` under a local Bun 1.4.3; #591 re-ran it under 1.4.2 and the diff is the version stamp only.
46
+ - `node scripts/check-docs.mjs --require-fresh`: `docs: OK (0 stale documents)` at the restamp.
47
+ - Codex P1 on #597 (stamps not an ancestor of a squashed commit) declined: `6f012880` is an ancestor of the PR merge ref and `--require-fresh` passes there; release PRs use a merge commit.
48
+ - Exact-head CI on the release PR.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
5
+ "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
14
+ "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
21
+ "verifiedAgainst": "ba26148f52511968910d5596572bc8ad7b5c6a3a",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -35,7 +35,7 @@
35
35
  ]
36
36
  },
37
37
  "docs/quickstart.md": {
38
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
38
+ "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
39
39
  "paths": [
40
40
  "package.json",
41
41
  "src/",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "docs/agent-notes/adapters.md": {
46
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
46
+ "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
47
47
  "paths": [
48
48
  "src/adapters/",
49
49
  "src/pi/",
@@ -113,7 +113,7 @@
113
113
  ]
114
114
  },
115
115
  "docs/agent-notes/daemon.md": {
116
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
116
+ "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
117
117
  "paths": [
118
118
  "src/hub/daemon.ts",
119
119
  "src/hub/control-client.ts",
@@ -188,7 +188,7 @@
188
188
  ]
189
189
  },
190
190
  "docs/agent-notes/tasks.md": {
191
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
191
+ "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
192
192
  "paths": [
193
193
  "src/hub/tasks.ts",
194
194
  "src/hub/board.ts",
@@ -215,7 +215,7 @@
215
215
  ]
216
216
  },
217
217
  "docs/agent-notes/tests.md": {
218
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
218
+ "verifiedAgainst": "ba26148f52511968910d5596572bc8ad7b5c6a3a",
219
219
  "paths": [
220
220
  "test/",
221
221
  "scripts/check.sh",
@@ -242,7 +242,7 @@
242
242
  ]
243
243
  },
244
244
  "src/cli/help.ts": {
245
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
245
+ "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
246
246
  "paths": [
247
247
  "src/cli/main.ts",
248
248
  "src/cli/launch.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.12.40",
3
+ "version": "0.12.42",
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.40",
3
+ "version": "0.12.42",
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",
@@ -1841,7 +1841,7 @@ var init_codex_appserver = __esm(() => {
1841
1841
  fetch: (req, server) => {
1842
1842
  if (req.headers.has("origin"))
1843
1843
  return new Response("forbidden", { status: 403 });
1844
- const launchId = new URL(req.url).searchParams.get("launchId");
1844
+ const launchId = /^Bearer (\S+)$/.exec(req.headers.get("authorization") ?? "")?.[1];
1845
1845
  if (server.upgrade(req, { data: { ...launchId && launchId.length <= 128 ? { launchId } : {} } }))
1846
1846
  return;
1847
1847
  return new Response("agent-hub codex proxy");
@@ -9190,6 +9190,14 @@ function runAgyTui(argv, cwd, env) {
9190
9190
  });
9191
9191
  });
9192
9192
  }
9193
+ function agyVersionGate(stdout) {
9194
+ const version = stdout.trim();
9195
+ if (version === "1.4.2")
9196
+ return { ok: true };
9197
+ if (/^1\.4\.\d+$/.test(version))
9198
+ return { ok: true, message: `agy ${version}: only 1.4.2 is T0-verified; launching on the 1.4.x line unverified` };
9199
+ return { ok: false, message: "agy P1 requires the Antigravity CLI 1.4.x line (1.4.2 verified)" };
9200
+ }
9193
9201
  async function runAgyEntry(ctx) {
9194
9202
  const { args, cwd, stateDir, cliDir, connect, fail } = ctx;
9195
9203
  if (ctx.instance)
@@ -9207,8 +9215,11 @@ async function runAgyEntry(ctx) {
9207
9215
  return;
9208
9216
  }
9209
9217
  const version = spawnSync4("agy", ["--version"], { encoding: "utf8", timeout: 5000, maxBuffer: 4096, env: peerChildEnv("agy") });
9210
- if (version.status !== 0 || version.stdout.trim() !== "1.4.2")
9211
- fail("agy P1 requires the verified Antigravity CLI 1.4.2");
9218
+ const gate = agyVersionGate(version.status === 0 ? version.stdout : "");
9219
+ if (!gate.ok)
9220
+ fail(gate.message);
9221
+ if (gate.message)
9222
+ console.error(gate.message);
9212
9223
  const hub = await connect();
9213
9224
  let options;
9214
9225
  let pluginWritten = false;
@@ -9396,22 +9407,20 @@ async function runCodexEntry(ctx) {
9396
9407
  ctx.executionCwd = res.executionCwd;
9397
9408
  }
9398
9409
  const control = readControl(stateDir);
9399
- let proxyUrl = res.proxyUrl;
9410
+ let launchTokenEnv;
9400
9411
  try {
9401
9412
  if (control?.instanceId) {
9402
9413
  const terminal = await prepareTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: ctx.executionCwd } : undefined);
9403
9414
  if (terminal) {
9404
9415
  await registerTerminalLaunch(hub, terminal, process.env.AGENTHUB_RECOVERY_OPERATION);
9405
- const url = new URL(proxyUrl);
9406
- url.searchParams.set("launchId", terminal.launchId);
9407
- proxyUrl = url.toString();
9416
+ launchTokenEnv = "AGENTHUB_LAUNCH_ID";
9408
9417
  }
9409
9418
  }
9410
9419
  } finally {
9411
9420
  hub.close();
9412
9421
  }
9413
9422
  const runningCodexBin = res.retainedLaunchConfig?.codex_bin;
9414
- const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
9423
+ const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, launchTokenEnv, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
9415
9424
  if (launch0.warning)
9416
9425
  console.error(launch0.warning);
9417
9426
  exec(launch.cmd, launch.args, "codex");
@@ -10426,7 +10435,7 @@ function codexNativeLaunch(ctx) {
10426
10435
  throw new Error("codex proxy url is missing");
10427
10436
  return nativeLaunchPlan({
10428
10437
  cmd: ctx.codexBin ?? "codex",
10429
- args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : [], ...passthrough],
10438
+ args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...ctx.launchTokenEnv ? ["--remote-auth-token-env", ctx.launchTokenEnv] : [], ...unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : [], ...passthrough],
10430
10439
  unattended,
10431
10440
  ...warning
10432
10441
  }, ctx);
@@ -10434,7 +10443,7 @@ function codexNativeLaunch(ctx) {
10434
10443
  var CODEX_LAUNCH_POLICY;
10435
10444
  var init_native_cli_codex_launch = __esm(() => {
10436
10445
  init_native_cli_launch_common();
10437
- CODEX_LAUNCH_POLICY = Object.freeze({ hubSwitches: Object.freeze(["--unattended", "--safe", "--new"]), ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]), unattended: true, passthrough: true });
10446
+ CODEX_LAUNCH_POLICY = Object.freeze({ hubSwitches: Object.freeze(["--unattended", "--safe", "--new"]), ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]), unattended: true, passthrough: true });
10438
10447
  });
10439
10448
 
10440
10449
  // src/hub/events.ts
@@ -10732,7 +10741,7 @@ var init_builtin_metadata = __esm(() => {
10732
10741
  conductorStartable: true,
10733
10742
  unattended: true,
10734
10743
  hubSwitches: switches,
10735
- ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10744
+ ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10736
10745
  },
10737
10746
  identity: { mcp: "env" },
10738
10747
  permission: {
@@ -10902,7 +10911,7 @@ var init_builtin_metadata = __esm(() => {
10902
10911
  conductorStartable: true,
10903
10912
  unattended: true,
10904
10913
  hubSwitches: switches,
10905
- ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10914
+ ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10906
10915
  },
10907
10916
  identity: { mcp: "env" },
10908
10917
  permission: {
@@ -18418,7 +18427,7 @@ init_spec_read();
18418
18427
  // package.json
18419
18428
  var package_default = {
18420
18429
  name: "@staix/agent-hub",
18421
- version: "0.12.40",
18430
+ version: "0.12.42",
18422
18431
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
18423
18432
  license: "MIT",
18424
18433
  type: "module",
@@ -33,6 +33,14 @@ function runAgyTui(argv: string[], cwd: string, env: NodeJS.ProcessEnv): Promise
33
33
  });
34
34
  }
35
35
 
36
+ /** agy auto-updates, so the 1.4.x line launches; only 1.4.2 has a T0 receipt and other patches warn (#589). */
37
+ export function agyVersionGate(stdout: string): { ok: true; message?: string } | { ok: false; message: string } {
38
+ const version = stdout.trim();
39
+ if (version === "1.4.2") return { ok: true };
40
+ if (/^1\.4\.\d+$/.test(version)) return { ok: true, message: `agy ${version}: only 1.4.2 is T0-verified; launching on the 1.4.x line unverified` };
41
+ return { ok: false, message: "agy P1 requires the Antigravity CLI 1.4.x line (1.4.2 verified)" };
42
+ }
43
+
36
44
  export async function runAgyEntry(ctx: NativeCliEntryContext): Promise<void> {
37
45
  const { args, cwd, stateDir, cliDir, connect, fail } = ctx;
38
46
  if (ctx.instance) fail("extra Antigravity TUI instances are not verified in P1");
@@ -45,7 +53,9 @@ export async function runAgyEntry(ctx: NativeCliEntryContext): Promise<void> {
45
53
  const argv = nativeArgs;
46
54
  if (preview) { console.log(JSON.stringify({ command: "agy", args: argv.map(publicArg), mode: "tui", permission: "unmanaged", mcp: ".agents/plugins/agent-hub/", writes: false })); return; }
47
55
  const version = spawnSync("agy", ["--version"], { encoding: "utf8", timeout: 5000, maxBuffer: 4096, env: peerChildEnv("agy") });
48
- if (version.status !== 0 || version.stdout.trim() !== "1.4.2") fail("agy P1 requires the verified Antigravity CLI 1.4.2");
56
+ const gate = agyVersionGate(version.status === 0 ? version.stdout : "");
57
+ if (!gate.ok) fail(gate.message);
58
+ if (gate.message) console.error(gate.message);
49
59
  const hub = await connect();
50
60
  let options: AgyMcpOptions | undefined;
51
61
  let pluginWritten = false;
@@ -145,7 +145,7 @@ export class CodexPeer extends BasePeer {
145
145
  fetch: (req, server) => {
146
146
  // Browsers always send Origin on WebSocket upgrades; the TUI never does.
147
147
  if (req.headers.has("origin")) return new Response("forbidden", { status: 403 });
148
- const launchId = new URL(req.url).searchParams.get("launchId");
148
+ const launchId = /^Bearer (\S+)$/.exec(req.headers.get("authorization") ?? "")?.[1]; // codex --remote-auth-token-env (#586)
149
149
  if (server.upgrade(req, { data: { ...(launchId && launchId.length <= 128 ? { launchId } : {}) } as Link })) return undefined;
150
150
  return new Response("agent-hub codex proxy");
151
151
  },
@@ -93,7 +93,7 @@ export const NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN: readonly AdapterDefinition
93
93
  api: 1, kind: "codex", displayName: "Codex", family: "codex", transport: "codex-appserver",
94
94
  roles: Object.freeze(["implementer"]), defaultOnRoster: true, trust: { onPrem: false, hubSandboxed: false },
95
95
  start: { create: createCodex, daemonPort: "codex-appserver", forms: dualForms, defaultForm: "tui", ownsTerminal: terminal, launch: codexNativeLaunch, version: codexObservedVersion, ...CODEX_LAUNCH_POLICY, conductorStartable: true,
96
- unattended: true, hubSwitches: switches, ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]) },
96
+ unattended: true, hubSwitches: switches, ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]) },
97
97
  identity: { mcp: "env" },
98
98
  permission: { trackUnattended: true, modes: { ask: { kind: "native-only", note: "restore the session's captured native approval policy" },
99
99
  "ask-when-needed": { kind: "codex-approval", policy: "on-request" }, "never-ask": { kind: "codex-approval", policy: "never" } },
@@ -180,7 +180,7 @@ export const BUILTIN_DEFINITIONS_WITHOUT_QWEN: readonly AdapterDefinition[] = Ob
180
180
  api: 1, kind: "codex", displayName: "Codex", family: "codex", transport: "codex-appserver",
181
181
  roles: Object.freeze(["implementer"]), defaultOnRoster: true, trust: { onPrem: false, hubSandboxed: false },
182
182
  start: { forms: dualForms, defaultForm: "tui", ownsTerminal: terminal, launch: ctx => compatibilityLaunch("codex", ctx), conductorStartable: true,
183
- unattended: true, hubSwitches: switches, ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]) },
183
+ unattended: true, hubSwitches: switches, ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]) },
184
184
  identity: { mcp: "env" },
185
185
  permission: { modes: { ask: { kind: "native-only", note: "restore the session's captured native approval policy" },
186
186
  "ask-when-needed": { kind: "codex-approval", policy: "on-request" }, "never-ask": { kind: "codex-approval", policy: "never" } },
@@ -1,13 +1,13 @@
1
1
  import type { LaunchContext, LaunchPlan } from "../registry-types.ts";
2
2
  import { nativeLaunchArguments, nativeLaunchPlan } from "./native-cli-launch-common.ts";
3
3
 
4
- export const CODEX_LAUNCH_POLICY = Object.freeze({ hubSwitches: Object.freeze(["--unattended", "--safe", "--new"]), ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]), unattended: true, passthrough: true });
4
+ export const CODEX_LAUNCH_POLICY = Object.freeze({ hubSwitches: Object.freeze(["--unattended", "--safe", "--new"]), ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"]), unattended: true, passthrough: true });
5
5
  export function codexNativeLaunch(ctx: LaunchContext): LaunchPlan {
6
6
  const { passthrough, unattended, warning } = nativeLaunchArguments("codex", ctx, CODEX_LAUNCH_POLICY);
7
7
  if (!ctx.proxyUrl) throw new Error("codex proxy url is missing");
8
8
  return nativeLaunchPlan({
9
9
  cmd: ctx.codexBin ?? "codex",
10
- args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...(unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : []), ...passthrough],
10
+ args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...(ctx.launchTokenEnv ? ["--remote-auth-token-env", ctx.launchTokenEnv] : []), ...(unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : []), ...passthrough],
11
11
  unattended,
12
12
  ...warning,
13
13
  }, ctx);
@@ -20,18 +20,18 @@ export async function runCodexEntry(ctx: NativeCliEntryContext): Promise<void> {
20
20
  ctx.executionCwd = res.executionCwd;
21
21
  }
22
22
  const control = readControl(stateDir);
23
- let proxyUrl = res.proxyUrl;
23
+ let launchTokenEnv: string | undefined;
24
24
  try {
25
25
  if (control?.instanceId) {
26
26
  const terminal = await prepareTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: ctx.executionCwd } : undefined);
27
27
  if (terminal) {
28
28
  await registerTerminalLaunch(hub, terminal, process.env.AGENTHUB_RECOVERY_OPERATION);
29
- const url = new URL(proxyUrl); url.searchParams.set("launchId", terminal.launchId); proxyUrl = url.toString();
29
+ launchTokenEnv = "AGENTHUB_LAUNCH_ID"; // codex --remote takes only ws://host:port, so the id rides the bearer header (#586)
30
30
  }
31
31
  }
32
32
  } finally { hub.close(); }
33
33
  const runningCodexBin = res.retainedLaunchConfig?.codex_bin;
34
- const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
34
+ const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, launchTokenEnv, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
35
35
  if (launch0.warning) console.error(launch0.warning);
36
36
  exec(launch.cmd, launch.args, "codex");
37
37
  }
@@ -29,6 +29,8 @@ export interface LaunchContext {
29
29
  userArgs: string[];
30
30
  unattended: boolean;
31
31
  proxyUrl?: string;
32
+ /** Env var holding the terminal launch id; Codex sends it as the proxy upgrade's bearer token (#586). */
33
+ launchTokenEnv?: string;
32
34
  codexBin?: string;
33
35
  statusLine?: StatusLineTee;
34
36
  facts?: FactsHook;
package/src/cli/help.ts CHANGED
@@ -51,7 +51,7 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
51
51
  ["ahub codex [--print-command] [--unattended] [args...]", "start the Codex adapter and attach the TUI"],
52
52
  ["ahub kimi [--print-command] [--mode tui|headless] [--model <alias>]", "default: Kimi's own TUI, pull-only via hub_inbox; no push, steer, watchdog, console approvals or automatic quota handoff. Select headless for ACP"],
53
53
  ["ahub jcode [--mode tui|headless] [--model <alias>] [--provider-profile <name>] [--session-id <id>] [--print-command]", "optional Jcode (roster.jcode, default off): its TUI on the hub-owned ACP session, opened once that session is saved; --mode headless for ACP only; native policy unmanaged, no hub sandbox"],
54
- ["ahub agy [--print-command | --dry-run] [args...]", "optional Antigravity CLI 1.4.2 TUI in this terminal (roster.agy, default off): pull-only via hub_inbox, implementer/verifier only; native permissions unmanaged, no hub sandbox"],
54
+ ["ahub agy [--print-command | --dry-run] [args...]", "optional Antigravity CLI 1.4.x TUI (1.4.2 verified) in this terminal (roster.agy, default off): pull-only via hub_inbox, implementer/verifier only; native permissions unmanaged, no hub sandbox"],
55
55
  ["ahub qwen [--mode headless] [--model <alias>] [--session-id <id>] [--print-command]", "start optional Qwen Code 0.25.0 ACP (roster.qwen, default off)"],
56
56
  ["ahub pi [--print-command] [--mode headless|tui | --headless] [--backend auto|dgx|mlx] [--model <alias>] [--session-id <id>] [--session-file <path>] [--replace-session]", "start Pi: its TUI in this terminal unless --headless or the start mode setting (peers.pi.start_mode) says headless; without a terminal the hub opens one or says how"],
57
57
  ["ahub local [--route <id> | --model <id>]", "start the hub-native worker on the self-hosted models (routing.toml)"],
package/src/cli/launch.ts CHANGED
@@ -329,7 +329,7 @@ export function sessionSettings(tee?: StatusLineTee, facts?: FactsHook): string
329
329
  export function buildLaunch(
330
330
  tool: "claude" | "codex",
331
331
  userArgs: string[],
332
- ctx: { instance?: string; unattended: boolean; proxyUrl?: string; codexBin?: string; statusLine?: StatusLineTee; facts?: FactsHook; preview?: boolean },
332
+ ctx: { instance?: string; unattended: boolean; proxyUrl?: string; launchTokenEnv?: string; codexBin?: string; statusLine?: StatusLineTee; facts?: FactsHook; preview?: boolean },
333
333
  ): Launch {
334
334
  const adapter = definitionOf(tool);
335
335
  if (!adapter) throw new Error("native launch definition unavailable");