@staix/agent-hub 0.12.40 → 0.12.41

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,12 @@
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.41
6
+
7
+ A one-fix release for a 0.12.40 regression.
8
+
9
+ - `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).
10
+
5
11
  ## 0.12.40
6
12
 
7
13
  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.41, 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,15 @@ 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).
88
89
  Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
89
90
  refuse while cancelled rows exist. Live-test delegated CLI commands need a daemon
90
91
  advertising `cli_audit_delegated` and refuse with an upgrade remedy otherwise
91
92
  (#410). For local/Pi implementation, opt in to an available
92
93
  `checks.runner = "container"`; issue-backed owners also need authenticated
93
94
  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
95
+ See [release verification](docs/verification/2026-10-20-0.12.41.md) for the live legs
95
96
  still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
96
97
  real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
97
98
  (#478), a live headless Claude review (#223), a Claude session over 32 MiB (#517), a
@@ -73,4 +73,4 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
73
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.
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.
@@ -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.41 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.41), 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.41 ahub upgrade --to 0.12.41 --dry-run
2055
+ bunx --package @staix/agent-hub@0.12.41 ahub upgrade --to 0.12.41 --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.41 (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.41 --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.41 --yes
2101
2101
  ahub recovery status [<operation-id>]
2102
2102
  ahub recovery resume [<operation-id>]
2103
2103
  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.12.40
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.41
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -30,9 +30,13 @@ 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.41
34
+
35
+ 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.
36
+
33
37
  ## Upgrading to 0.12.40
34
38
 
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).
39
+ Restart Claude sessions after upgrading: each keeps the plugin server it started with until restarted, so a restart loads the 0.12.41 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
40
 
37
41
  `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
42
 
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.
@@ -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.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
5
+ "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
21
+ "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
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.41",
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.41",
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");
@@ -9396,22 +9396,20 @@ async function runCodexEntry(ctx) {
9396
9396
  ctx.executionCwd = res.executionCwd;
9397
9397
  }
9398
9398
  const control = readControl(stateDir);
9399
- let proxyUrl = res.proxyUrl;
9399
+ let launchTokenEnv;
9400
9400
  try {
9401
9401
  if (control?.instanceId) {
9402
9402
  const terminal = await prepareTerminalLaunch(peer, cwd, stateDir, control.instanceId, ctx.instance ? { executionCwd: ctx.executionCwd } : undefined);
9403
9403
  if (terminal) {
9404
9404
  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();
9405
+ launchTokenEnv = "AGENTHUB_LAUNCH_ID";
9408
9406
  }
9409
9407
  }
9410
9408
  } finally {
9411
9409
  hub.close();
9412
9410
  }
9413
9411
  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 });
9412
+ const launch = buildLaunch("codex", args, { unattended: unattendedEnv, instance: peer, proxyUrl: res.proxyUrl, launchTokenEnv, codexBin: typeof runningCodexBin === "string" ? runningCodexBin : projectConfig().codex_bin });
9415
9413
  if (launch0.warning)
9416
9414
  console.error(launch0.warning);
9417
9415
  exec(launch.cmd, launch.args, "codex");
@@ -10426,7 +10424,7 @@ function codexNativeLaunch(ctx) {
10426
10424
  throw new Error("codex proxy url is missing");
10427
10425
  return nativeLaunchPlan({
10428
10426
  cmd: ctx.codexBin ?? "codex",
10429
- args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : [], ...passthrough],
10427
+ args: ["--enable", "tui_app_server", "--remote", ctx.proxyUrl, ...ctx.launchTokenEnv ? ["--remote-auth-token-env", ctx.launchTokenEnv] : [], ...unattended ? ["--dangerously-bypass-approvals-and-sandbox"] : [], ...passthrough],
10430
10428
  unattended,
10431
10429
  ...warning
10432
10430
  }, ctx);
@@ -10434,7 +10432,7 @@ function codexNativeLaunch(ctx) {
10434
10432
  var CODEX_LAUNCH_POLICY;
10435
10433
  var init_native_cli_codex_launch = __esm(() => {
10436
10434
  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 });
10435
+ 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
10436
  });
10439
10437
 
10440
10438
  // src/hub/events.ts
@@ -10732,7 +10730,7 @@ var init_builtin_metadata = __esm(() => {
10732
10730
  conductorStartable: true,
10733
10731
  unattended: true,
10734
10732
  hubSwitches: switches,
10735
- ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10733
+ ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10736
10734
  },
10737
10735
  identity: { mcp: "env" },
10738
10736
  permission: {
@@ -10902,7 +10900,7 @@ var init_builtin_metadata = __esm(() => {
10902
10900
  conductorStartable: true,
10903
10901
  unattended: true,
10904
10902
  hubSwitches: switches,
10905
- ownedFlags: Object.freeze(["--remote", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10903
+ ownedFlags: Object.freeze(["--remote", "--remote-auth-token-env", "--yolo", "--dangerously-bypass-approvals-and-sandbox"])
10906
10904
  },
10907
10905
  identity: { mcp: "env" },
10908
10906
  permission: {
@@ -18418,7 +18416,7 @@ init_spec_read();
18418
18416
  // package.json
18419
18417
  var package_default = {
18420
18418
  name: "@staix/agent-hub",
18421
- version: "0.12.40",
18419
+ version: "0.12.41",
18422
18420
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
18423
18421
  license: "MIT",
18424
18422
  type: "module",
@@ -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/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");