@staix/agent-hub 0.12.21 → 0.12.23
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 +29 -0
- package/README.md +27 -12
- package/docs/agent-notes/adapters.md +15 -7
- package/docs/agent-notes/benchmarks.md +4 -1
- package/docs/agent-notes/budget.md +4 -0
- package/docs/agent-notes/bus.md +2 -1
- package/docs/agent-notes/daemon.md +12 -1
- package/docs/agent-notes/local-worker.md +4 -1
- package/docs/agent-notes/tasks.md +2 -1
- package/docs/agent-notes/tests.md +4 -0
- package/docs/events.md +17 -4
- package/docs/operations.md +478 -41
- package/docs/quickstart.md +14 -6
- package/docs/security.md +23 -11
- package/docs/smoke.md +133 -7
- package/docs/specs/2026-09-19-agent-hub-design.md +115 -15
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +84 -8
- package/docs/verification/2026-10-10-cli-output-phase1.md +309 -0
- package/docs/verification/2026-10-10-cli-output-phase2.md +672 -0
- package/docs/verification/2026-10-11-0.12.23.md +90 -0
- package/docs/verified.json +32 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +14 -3
- package/src/adapters/acp-quota.ts +13 -0
- package/src/adapters/acp.ts +105 -17
- package/src/adapters/codex-appserver.ts +70 -35
- package/src/adapters/local-worker.ts +44 -10
- package/src/adapters/pi.ts +158 -19
- package/src/cli/bench.ts +2 -1
- package/src/cli/console-state.ts +28 -22
- package/src/cli/console.ts +20 -10
- package/src/cli/help.ts +25 -19
- package/src/cli/identity.ts +3 -3
- package/src/cli/init.ts +1 -1
- package/src/cli/launch.ts +182 -7
- package/src/cli/main.ts +409 -177
- package/src/cli/output.ts +363 -0
- package/src/cli/preview.ts +2 -1
- package/src/cli/recovery-package.ts +17 -0
- package/src/cli/reset.ts +3 -0
- package/src/cli/status-lines.ts +0 -28
- package/src/cli/terminal-recovery.ts +27 -1
- package/src/cli/upgrade-interactive.ts +297 -0
- package/src/cli/upgrade-runtime.ts +80 -9
- package/src/cli/upgrade.ts +50 -15
- package/src/hub/board.ts +2 -0
- package/src/hub/budget.ts +39 -7
- package/src/hub/bus.ts +15 -14
- package/src/hub/child-process.ts +11 -0
- package/src/hub/conductor.ts +23 -11
- package/src/hub/config-trust.ts +16 -5
- package/src/hub/conflicts.ts +2 -0
- package/src/hub/context-window.ts +8 -1
- package/src/hub/crash.ts +2 -1
- package/src/hub/daemon.ts +483 -83
- package/src/hub/db-timeout.ts +3 -0
- package/src/hub/delivery-journal.ts +2 -1
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +4 -1
- package/src/hub/execution-budget.ts +2 -0
- package/src/hub/facts.ts +5 -4
- package/src/hub/git.ts +32 -0
- package/src/hub/hub-tools.ts +1 -1
- package/src/hub/lifecycle.ts +10 -4
- package/src/hub/manager.ts +18 -5
- package/src/hub/peers.ts +7 -5
- package/src/hub/permission-mode.ts +14 -0
- package/src/hub/project.ts +2 -1
- package/src/hub/recovery-error.ts +31 -0
- package/src/hub/recovery-store.ts +43 -4
- package/src/hub/registry.ts +2 -1
- package/src/hub/routing.ts +114 -13
- package/src/hub/settings.ts +392 -0
- package/src/hub/snapshots.ts +4 -2
- package/src/hub/start-mode.ts +48 -0
- package/src/hub/tasks.ts +158 -43
- package/src/hub/terminal-open.ts +64 -0
- package/src/hub/ui.ts +25 -13
- package/src/local/deny.ts +71 -2
- package/src/local/sandbox.ts +47 -6
- package/src/local/tools.ts +25 -6
- package/src/memory/recall.ts +2 -2
- package/src/models/mlx.ts +2 -1
- package/src/pi/extension.ts +43 -20
- package/src/pi/tool-receipts.ts +19 -2
- package/src/ui/index.html +172 -21
package/docs/quickstart.md
CHANGED
|
@@ -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.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.12.23
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -26,7 +26,9 @@ From a clone instead: `git clone`, `bun install`, `bun link`.
|
|
|
26
26
|
`ahub doctor` tells you what is installed, running and configured. Rows you do not need can stay red: the hub works with any subset of the peers.
|
|
27
27
|
|
|
28
28
|
For managed launchers, two-agent task/review work, approvals, graceful shutdown,
|
|
29
|
-
and upgrade or crash recovery, see the [operations guide](operations.md).
|
|
29
|
+
and upgrade or crash recovery, see the [operations guide](operations.md). To
|
|
30
|
+
move running projects to a newer release later, run `ahub upgrade` in a
|
|
31
|
+
terminal: it reviews the plan with you, applies it and follows it.
|
|
30
32
|
|
|
31
33
|
## First session
|
|
32
34
|
|
|
@@ -57,7 +59,7 @@ ahub say @kimi "run the tests and report" # one peer
|
|
|
57
59
|
{ "omniroute": { "urls": ["http://your-gateway:20128/v1"], "api_key_file": "/path/to/key" } }
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`) unless you chose a permission mode for it (`ahub permission`), and everything it executes is sandboxed.
|
|
62
|
+
or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`) unless you chose a permission mode for it (`ahub permission`), and everything it executes is sandboxed. Keep `ahub console` open while it works: a request nobody answers expires, and after two in a row its turn ends.
|
|
61
63
|
|
|
62
64
|
## Commands of a working day
|
|
63
65
|
|
|
@@ -68,6 +70,7 @@ or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local]
|
|
|
68
70
|
| `ahub board` | who has what; `ahub task show <id>` for one task |
|
|
69
71
|
| `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
|
|
70
72
|
| `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
|
|
73
|
+
| `ahub stop <peer>` | stop one headless peer (Kimi, headless Pi, `local`); the hub and the other peers keep running |
|
|
71
74
|
| `ahub kill` | stop the daemon and everything it started |
|
|
72
75
|
|
|
73
76
|
More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-design.md`](specs/2026-09-19-agent-hub-design.md). What it protects and what it does not: [`docs/security.md`](security.md).
|
|
@@ -83,7 +86,11 @@ Use the page to pause/resume a peer, send a console message, propose/assign task
|
|
|
83
86
|
and allow/deny agent permission requests. Local-worker and Pi requests show no tool
|
|
84
87
|
contents: inspect them with `ahub tail`, then use `ahub permit <id> <option>` to
|
|
85
88
|
allow. They can be denied on the page. PII task details remain available only via
|
|
86
|
-
`ahub task show <id>` in the terminal.
|
|
89
|
+
`ahub task show <id>` in the terminal. The page also starts a peer in its start
|
|
90
|
+
mode (a TUI opens in a terminal when the hub has a terminal provider) and shows
|
|
91
|
+
the settings; raising one needs `ahub ui --settings` or `ahub settings` in a
|
|
92
|
+
terminal. See the operations guide, "Settings from the dashboard and the
|
|
93
|
+
terminal" and "Start modes".
|
|
87
94
|
|
|
88
95
|
`ahub ui --no-open` prints a one-time link instead of launching a browser. Open it
|
|
89
96
|
within 60 seconds. A session expires after one hour; run `ahub ui` again. Restarting
|
|
@@ -105,8 +112,9 @@ commands use the nearest project inside that Git working-tree boundary.
|
|
|
105
112
|
|
|
106
113
|
Open `ahub ui --all` from any directory to manage all registered hubs. Use Start
|
|
107
114
|
to launch an initialized project, Use to view a running project, and Stop to stop
|
|
108
|
-
the selected hub. Stop confirmation names its root.
|
|
109
|
-
still `ahub --project <path> claude` or `ahub --project <path> codex
|
|
115
|
+
the selected hub. Stop confirmation names its root. Without a terminal provider,
|
|
116
|
+
agent launch is still `ahub --project <path> claude` or `ahub --project <path> codex`
|
|
117
|
+
in a terminal.
|
|
110
118
|
|
|
111
119
|
The manager is independent: `ahub ui --all --stop` closes its browser sessions
|
|
112
120
|
without stopping project hubs. One-time links and session expiry work the same
|
package/docs/security.md
CHANGED
|
@@ -6,14 +6,14 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
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
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.
|
|
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. `--unattended` turns prompts off, says so loudly, and is never the default.
|
|
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`) apply only from a config file git confirms nobody committed: `.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, 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.
|
|
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
|
+
- **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`) apply only from a config file git confirms nobody committed: `.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 go through the one helper in `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 and the credential and askpass helpers are not (every call pipes stdout, and no hub command authenticates), and their settings are belt and suspenders. 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.
|
|
12
12
|
- **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
|
|
13
13
|
- **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
|
|
14
14
|
- **The session record holds identities only.** `.agenthub/state/sessions.json` (issue #37, mode 600) keeps each attached peer's recovery metadata: launch options, session and thread ids, Pi's session file path. No message or task text; loss notices name deliveries by id, sender and public task title.
|
|
15
15
|
- **Context checkpoints have a separate text record.** Native occupancy readings carry numeric metadata only. An optional context checkpoint persists its summary in `.agenthub/state/context-checkpoint-<peer>.json` with mode 0600 and attempts shared-memory storage when enabled. Completion requires the outstanding request id, current peer/native session and transport generation; new connection claims invalidate old requests. Private turns, every open PII task associated with the peer as owner or reviewer (including pending review), and PII-pattern summaries are refused before persistence or cloud memory. Its text is never broadcast, and context pressure does not pause, reassign or replace the session.
|
|
16
|
-
- **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git/hooks` or `.git
|
|
16
|
+
- **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git` name at any depth, closing the creation, rename, symlink and gitfile routes to a planted repository, no `config` or `hooks` directly under one nor below the discovered modules root of the project's own `.git` (a submodule's git directory, nested submodules included; every term is anchored below that root, so a directory named `refs`, `config` or `hooks` above the project neither disables the rule nor trips it; a `/refs/` or `/logs/` segment below it keeps refs named `config` or `hooks` writable, and leaves a submodule whose own path contains such a segment uncovered; a submodule whose path has `hooks` as a whole component (`tools/hooks` included) fares worse: the rule matches its whole git directory, so every write there is refused; the discovered git directories, the modules root and every component in between are also refused as targets, closing the rename, replace and symlink routes to the same files), no `commondir` at any depth below one, with every such name matched folded against the code points HFS+ ignores (the fold covers the security-relevant names; on a volume that folds names, folded spellings of the components between `.git` and a submodule's git directory are not matched — the 1024-byte SBPL literal cannot hold a folded path — the same residual the external git dir rules carry since #270), and no `.agenthub` anywhere in the project (in an external git dir the `config`/`hooks` rules cover only its own children and the hooks directory's contents, so refs named `config` or `hooks` still work); a nested repository in bare layout stays open — the directory is the git dir, with no `.git` name to anchor a rule on, and the hub never runs git there, whatever the project root is: its calls are all `-C <project root>` or `--no-index` on temp files, and none descends into a nested directory's git layout, so a planted hook fires only if a person or another tool runs git in that directory themselves; the rules are probed on HFS+, which folds the 16 code points the patterns tolerate, and on a case-sensitive APFS image, which folds nothing (a folded spelling is an inert new file there and the real names stay refused); the cost is that `git init`, `git clone` and `git worktree add` do not run inside the sandbox (add, commit, stash, checkout and gc still do), `git submodule update --init` does not either (its git directory's `config` write is refused), and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` in a project with a nested repository, or `rm -rf` of a directory that holds a `.git` (refusing the unlink is what stops the rename-out route); no network, loopback included, unless `local.bash_network`; with it, only through the hub's egress proxy to the hosts in `local.network_allow` (issue #65), which refuses names that resolve to internal addresses (behind NAT64 with the well-known prefix, by the IPv4 address the answer carries; a network-specific prefix is not recognised), so claude-mem and the Codex app-server stay out of reach (`"direct"` opens everything, until 0.13.0). One channel stays open: `trustd`, which TLS clients need, can fetch a certificate's AIA or OCSP URL on a command's behalf, outside the proxy. The allow-default profile of 0.9 and earlier was removed in 0.12.0 (issue #83); a `local.sandbox` setting is ignored with a note. The shared temp dirs (the user's and `/private/tmp`) are closed. Without the sandbox there is no `bash` tool.
|
|
17
17
|
- **Capabilities are enforced, not suggested.** `capabilities` (issue #39) is checked by the daemon where task operations and messages arrive, so a peer cannot get round it by phrasing. A peer can never answer a permission request: the control link takes `permit` from the console role only, and a message that quotes a permit command is just text. Both bind the hub's own tool paths: a vendor agent with its own shell in the project (Codex, Claude, Kimi) can read `.agenthub/state/control-token` and connect as the console, which only the local worker's and Pi's sandbox prevents.
|
|
18
18
|
- **PII has an enforced path.** A task matching `signals.pii_patterns` goes to `local` or to nobody; its text is absent from other peers' envelopes, the console stream, the log and the board listing; the console user reviews it; `local` answers such a turn to the console only, keeps it out of its history, refuses it when the only gateway is off campus, and may not save notes or spin off tasks during it. Nothing about it is sent to claude-mem, whose observer is a cloud model.
|
|
19
19
|
- **A model screen can add to the patterns (issue #198).** With `signals.pii_screen = "local"` in `routing.toml` (default `off`; it applies while `constraints.pii = "local_only"`), a newly proposed task is read by a model before the hub does anything else with it: until the verdict it is on no board, in no envelope, before no triage model, and the hub makes no brief or note call for it. The local worker's claude-mem capture leaves its hub tool calls out (issue #230), so a proposal it makes in an ordinary turn cannot reach claude-mem ahead of the verdict that way. The screen runs on the model on this machine (MLX or Ollama, loopback only) or, without one, on the gateway only while `onCampus()` holds, and the gateway call is refused once more right before transport if the gateway is behind Access; task text never goes off campus to be screened. When the on-device generation slot is taken (Pi generating, another screen), the screen goes to the campus gateway instead of waiting, and on campus a device that fails or is still loading after 60% of the deadline hands over to the gateway too; off campus it waits for the slot and the device within the deadline. With the legacy MLX provider the screen may start an `mlx_lm.server`, as Pi's relay does, and it stays running until `ahub models stop`. Its answer is `pii` or `clear` plus a category from a closed list (name, student id, phone, address, grade, health, other). Anything else, a timeout (8 s, one call and one generation slot per item), a missing or off-campus model, or text longer than 6000 UTF-8 bytes (about 2000 Hangul syllables) is `unknown`, and an unknown task is a PII task: this is the hub's one model call that fails closed, so with the screener down every new task waits for `local`. An unknown verdict has the costs of any PII task, even when a second look clears it later: opening it lifts every silent turn-free cohort for good and turns facts off while it is open, and off campus a proposal without a class is refused (triage is not asked for a PII task there), with an error that says so. Off campus with the on-device slot taken, a task stays `unknown` until it is screened again: while it is still proposed and no peer ever owned it, the hub's release timer (every 60 s, one task at a time, the least tried first, at most 10 times per task in a hub run; never text too long to read) asks again, and `clear` lifts its `pii` signal and routes it through the class peers (a named owner is not remembered; a reserved one is offered first, as always). A task `local` took keeps its PII path, also once it is declined back to nobody. A pattern match is PII without a call. The task's history records the source of its `pii` signal (`regex`, `screen` with the category, or `unknown` with the reason), an unknown verdict is a console line naming the task id only, and `events.jsonl` gets a `pii_screen` record with the item kind, label, source, category, reason and latency, never the text. A routing explanation of a draft does not call the screen and says so.
|
|
@@ -57,8 +57,9 @@ are passed by path, keeping caller settings out of process argv.
|
|
|
57
57
|
Claude ask-when-needed grants only resolving project file targets outside
|
|
58
58
|
.agenthub, .git, .claude, .codex, .qwen, .kimi, .pi and .mcp.json. Symlink escapes, protected aliases, traversal and
|
|
59
59
|
unresolved wildcard targets receive no decision. Names are compared folded (letter case, compatibility forms and
|
|
60
|
-
the code points
|
|
61
|
-
is the same name), the path is judged inside the project, and
|
|
60
|
+
the code points HFS+ ignores; case-insensitive APFS folds case but ignored none in the probe; so a look-alike such
|
|
61
|
+
as a long s or a name with a zero-width joiner is the same name), the path is judged inside the project, and
|
|
62
|
+
a file with a second hard link receives no decision,
|
|
62
63
|
for Claude and for the Pi/local write and edit grants alike. The local worker's and Pi's write guard refuses `.git`
|
|
63
64
|
and `.agenthub` by the same folded comparison in every mode, so a new `.GIT/config` cannot be planted for git to run. Native rules then apply;
|
|
64
65
|
this is a grant filter, not a filesystem sandbox.
|
|
@@ -72,7 +73,8 @@ as that peer in tools mode. Messages and task changes retain that actor;
|
|
|
72
73
|
capability. Conflicting or malformed markers fail closed. There is no `--as-user`.
|
|
73
74
|
Human-only operations are refused before connecting, including permission answers
|
|
74
75
|
and permission modes, queue resolution, budget overrides, benchmark runs, research
|
|
75
|
-
backfill and task labels,
|
|
76
|
+
backfill and task labels, stopping a peer (`ahub stop`), lifecycle and recovery
|
|
77
|
+
operations, and `ask`.
|
|
76
78
|
The operator uses a plain terminal or `ahub console`. A Claude `!` command that
|
|
77
79
|
inherits the agent markers follows the same rule. The console strips control
|
|
78
80
|
sequences from agent and daemon text before it quotes forged headers, and its
|
|
@@ -90,9 +92,11 @@ no role: a task's current owner and reviewer may read its public view with
|
|
|
90
92
|
`hub_task_assign` while it is proposed, nobody ever accepted it and its latest
|
|
91
93
|
move other than the hub's own was not made from the console (to another peer
|
|
92
94
|
only with `assign` when its capabilities are listed).
|
|
93
|
-
A conductor may inspect public state, assign or escalate work, start
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
A conductor may inspect public state, assign or escalate work, start a peer in
|
|
96
|
+
the start mode a person set (it cannot choose the mode) and place its own
|
|
97
|
+
delivery holds. It cannot answer approvals,
|
|
98
|
+
resolve durable deliveries, override budget pauses, release a human hold or stop
|
|
99
|
+
a peer: `peer_stop` is taken from the console role only, and no hub tool sends it.
|
|
96
100
|
Pending approval summaries exclude titles; PII tasks remain public stubs.
|
|
97
101
|
Conduct events contain ids only. Supervision feeds use structured reasons and
|
|
98
102
|
never carry check output or approval bodies.
|
|
@@ -108,10 +112,18 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
|
|
|
108
112
|
- The exchange creates a separate random session, valid for one hour without renewal, in an `HttpOnly; SameSite=Strict; Path=/` cookie named for this listener's port. Sessions and tickets live only in memory and die with the daemon. The page itself keeps one more cookie, `agent-hub-theme` (issue #246): script-set, `SameSite=Strict`, kept for a year for the whole `127.0.0.1` host, and read back only as one of `system`, `light` or `dark`. HTTP is loopback-only; the cookie is not a substitute for the origin checks.
|
|
109
113
|
- Every request must have the exact listener Host. Every data or action request, including the ticket exchange and snapshot polling, must be POST with the exact listener Origin and JSON content type. Foreign and missing origins, missing or expired sessions, oversized bodies and unknown actions are rejected. There is no CORS support. The only unauthenticated GET is the fixed, data-free HTML shell. No files or paths are served dynamically.
|
|
110
114
|
- Responses are non-cacheable, cannot be framed, suppress referrers, and use a content security policy restricting scripts/styles to the shipped inline content and connections to this origin. Browser text is rendered with textContent, never interpreted as HTML.
|
|
111
|
-
- The browser receives a bounded redacted event stream, peer states and queue counts, public task views, budget windows, pending approvals and benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text. Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted.
|
|
112
|
-
- The closed action list is permission response, peer pause/resume, console message, task proposal and
|
|
115
|
+
- The browser receives a bounded redacted event stream, peer states and queue counts, public task views, budget windows, pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), and benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text. Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted.
|
|
116
|
+
- The closed action list is permission response, peer pause/resume, console message, task proposal, task assignment and, since issue #269, the settings actions below (set, undo, preview). These use the existing daemon/task paths and budget pause rules. `start_peer` (issue #269) starts a peer from a closed list in the mode a person set, with a command the hub assembles: the request carries a peer id and nothing else. There is no generic control proxy, task-detail read, shell, file access, free-form configuration edit, budget override, launch of a caller-chosen command or daemon shutdown API.
|
|
113
117
|
- The listener stops with the daemon. Expired sessions must be reopened with `ahub ui`; the page does not silently obtain new credentials.
|
|
114
118
|
|
|
119
|
+
## Dashboard settings and the settings session (issue #269)
|
|
120
|
+
|
|
121
|
+
- Settings are a closed registry (`src/hub/settings.ts`). The dashboard and `ahub settings` can write only its keys, each checked against its own closed values, and only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`. A file git tracks is never written, so the shared `config.json` and `routing.toml` stay byte-identical; in a repository where git cannot answer, nothing is written either. Commands and binaries (`kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`), credential files and gateway URLs (`omniroute`, `memory.worker_url`), the local worker's reach (`local.read_allow`, `local.bash_network`, `local.network_allow`), `bench.enabled` and `terminal` are refused by name from every surface: they choose what the hub runs or where secrets and task text go.
|
|
122
|
+
- `routing.local.toml` holds `stay_switch` and, per class, `peers`, `escalate_to`, `route` (an id `routing.toml` defines) and `pi_backend`, and nothing else: targets, routes, signals and the PII constraint cannot be set through it. A write is read by the hub's own loader before it replaces the file (temp file and rename), and one previous version is kept for undo.
|
|
123
|
+
- An ordinary dashboard session (`ahub ui`) reads settings, lowers a mode to `ask`, turns `pi.auto_start` off and changes the safe switches (`research.enabled`, `approvals.notify`, `snapshots.enabled`, `coordination`). Whatever can widen what agents do without asking needs a settings session: a non-ask mode now or at hub start, routing, starting Pi with the hub, removing a machine-local value, and undo. `ahub ui --settings` opens one: its ticket marks the session for 15 minutes, after which it is an ordinary session. `never-ask` also needs the peer's id typed as the confirmation, and a stored never-ask default still waits for the console's `y` at each hub start. An undo never puts never-ask back, and removing a machine-local value that uncovers a never-ask in the shared file needs the same confirmation. "Lowering to `ask`" means handing the decision back to the agent's own rules: for Codex that is the approval policy the person configured natively, which the hub does not judge.
|
|
124
|
+
- A settings session is as strong as the console for these settings and no stronger elsewhere. A mode change is the console's own request with the same refusals; tool approvals stay deny-only for Pi and local; there is still no shell, file, task-detail or launch-command API. `ahub ui --settings` and `ahub settings` pass the same identity gate as `ahub permission`, which is an operating policy, not authentication. The hostile-agent boundary above applies unchanged: an agent with an unrestricted shell as the same user can impersonate the console, and so can open a settings session.
|
|
125
|
+
- Every change writes a `settings` event (the key, the value before and after, the source `dashboard` or `terminal`) and a line in `hub.log` and open consoles; opening a settings session is logged too. The unified dashboard (`ahub ui --all`) forwards settings actions as an ordinary session and opens no settings session.
|
|
126
|
+
- Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
|
|
115
127
|
|
|
116
128
|
## Multi-project manager (protocol 7)
|
|
117
129
|
|
package/docs/smoke.md
CHANGED
|
@@ -2,6 +2,103 @@
|
|
|
2
2
|
|
|
3
3
|
`scripts/check.sh` covers everything against fakes. The legs below need real accounts and an interactive terminal, so they are run by hand and recorded here.
|
|
4
4
|
|
|
5
|
+
## Settings from the dashboard and the terminal (#269, part 1)
|
|
6
|
+
|
|
7
|
+
Not run live yet (unverified). `test/settings.test.ts` drives a real daemon, its
|
|
8
|
+
dashboard listener over HTTP and the real CLI; no browser was opened and no
|
|
9
|
+
native agent was started. `ahub ui --settings` and `ahub settings` are a
|
|
10
|
+
person's commands, so the live leg is run by hand, on a scratch project:
|
|
11
|
+
|
|
12
|
+
1. `ahub up`, then `ahub ui`. The Settings section lists Permissions, Start,
|
|
13
|
+
Routing and Switches; each row shows its source file and when it applies.
|
|
14
|
+
Record the look in a light and a dark theme and at phone width.
|
|
15
|
+
2. In that ordinary session, set `research.enabled` (saved), then try to raise
|
|
16
|
+
`permission.kimi` to ask-when-needed (refused, naming `ahub ui --settings`).
|
|
17
|
+
3. `ahub ui --settings`. With Kimi attached, set `permission.kimi` to
|
|
18
|
+
ask-when-needed, then never-ask with the peer id typed; `ahub permission`
|
|
19
|
+
and `ahub status` show each mode, and `ahub tail` shows each change with
|
|
20
|
+
`(dashboard)`.
|
|
21
|
+
4. Preview a new order for `routing.classes.implement.peers` with a task open,
|
|
22
|
+
save it, run `ahub route explain --class implement x` (it names
|
|
23
|
+
`routing.local.toml`), then "Undo last write".
|
|
24
|
+
5. `git status` shows no change to `.agenthub/config.json` or
|
|
25
|
+
`.agenthub/routing.toml`; `ahub settings` in the terminal shows the same
|
|
26
|
+
rows as the page.
|
|
27
|
+
|
|
28
|
+
Record the date, the hub version, the browser and each step's result.
|
|
29
|
+
|
|
30
|
+
Part 2, start modes (not run live either; `test/start-modes.test.ts` uses a
|
|
31
|
+
recording terminal provider and a scripted hub, and no terminal was opened):
|
|
32
|
+
|
|
33
|
+
6. In an Orca terminal of the project, `ahub up` with Pi enabled. `ahub pi`
|
|
34
|
+
opens Pi's TUI in that terminal; in another, `ahub pi --headless` moves it
|
|
35
|
+
to the background.
|
|
36
|
+
7. Stop Pi. In the dashboard's Peers panel the Start control for pi says
|
|
37
|
+
"Start mode tui: opens ahub pi --mode tui in a terminal through orca".
|
|
38
|
+
Press it: a new Orca terminal runs the TUI and Pi attaches.
|
|
39
|
+
8. In a settings session set `peers.pi.start_mode` to headless, stop Pi, press
|
|
40
|
+
Start again: Pi comes up headless and no terminal opens.
|
|
41
|
+
9. Outside Orca with no `terminal.open`: the Start control shows no button and
|
|
42
|
+
the command to run; typing `pi` in `ahub console` prints the same refusal.
|
|
43
|
+
10. With `terminal.open` set to a tmux or wezterm template in
|
|
44
|
+
`config.local.json`, Start opens Pi's TUI there.
|
|
45
|
+
|
|
46
|
+
## Codex resumed approval policy (#270)
|
|
47
|
+
|
|
48
|
+
2026-10-10: installed version read back as `codex-cli 0.162.1`. The live
|
|
49
|
+
stickiness leg is unverified: a hand-run `codex --remote` resume and a human-only
|
|
50
|
+
`ahub permission codex ask` require a person's interactive terminal. The approved
|
|
51
|
+
fallback is implemented: keep restoration owed per thread and send the captured
|
|
52
|
+
native policy once on that thread's next accepted turn, whether or not the native
|
|
53
|
+
override is sticky across resume. Fake app-server coverage verifies both overlays,
|
|
54
|
+
other-thread visits, either resume/ask order, rejected restoration and restoration
|
|
55
|
+
only once. The proxy protects the one restoring turn; a repeated policy on the
|
|
56
|
+
next TUI turn is indistinguishable from a person's explicit native choice.
|
|
57
|
+
|
|
58
|
+
Manual leg in a disposable project:
|
|
59
|
+
|
|
60
|
+
1. Start `ahub codex`, record its thread ID and native approval policy, then run
|
|
61
|
+
`ahub permission codex never-ask --yes` from a human shell and complete a benign turn.
|
|
62
|
+
2. Close the TUI without stopping its hub proxy. Run `ahub permission codex ask`.
|
|
63
|
+
3. Hand-run `codex --remote <the existing proxy URL>` and resume that same thread.
|
|
64
|
+
Record what the TUI sends as `approvalPolicy` on its first turn after a resume,
|
|
65
|
+
including any echo of the resume response. Observe the policy before and after
|
|
66
|
+
that benign turn; record whether the override persisted across resume and
|
|
67
|
+
whether the original policy was restored.
|
|
68
|
+
4. Send the TUI's second turn after that resume; record its outgoing policy and
|
|
69
|
+
the effective native policy. A repeat of the reported override is passed through
|
|
70
|
+
after the one restoring turn, just like an explicit choice.
|
|
71
|
+
5. Repeat with `ask-when-needed` and with resume before switching to ask. Record
|
|
72
|
+
the Codex version, thread ID, policies and outcomes. This checklist is not
|
|
73
|
+
evidence that any live leg has passed.
|
|
74
|
+
|
|
75
|
+
## Interactive upgrade (#272)
|
|
76
|
+
|
|
77
|
+
Not run live yet. Covered by tests with a scripted terminal and, for `restart`,
|
|
78
|
+
by a real detached operation driven through the plan screen
|
|
79
|
+
(`test/upgrade-cli.test.ts`, with standard input and output marked as a terminal).
|
|
80
|
+
The live leg, at a real terminal with Orca-launched peers:
|
|
81
|
+
|
|
82
|
+
1. With a hub on the previous release, run
|
|
83
|
+
`bunx --package @staix/agent-hub@<version> ahub upgrade`. Expect the plan
|
|
84
|
+
screen: the project, each peer's fate, `in progress:` when a peer is busy, no
|
|
85
|
+
blocker.
|
|
86
|
+
2. Press `a`. Expect the steps in order, `waiting for:` while a peer is busy, and
|
|
87
|
+
`upgrade to <version> completed`; `ahub --version` and `ahub status` report it.
|
|
88
|
+
3. On a scratch project, start `ahub restart` while a peer is busy, press `a`,
|
|
89
|
+
then Enter during the wait and `c` on the operation screen. Expect `operation
|
|
90
|
+
<id> is cancelled; the recovery lock is free`, the reset question, and the hub
|
|
91
|
+
still running.
|
|
92
|
+
4. With an installed CLI one release behind, run `ahub upgrade`, confirm the
|
|
93
|
+
hand-over, press `a`, then Ctrl+C during the wait. Expect the `left:` line, a
|
|
94
|
+
clean shell prompt, and `ahub recovery` showing the runner still working.
|
|
95
|
+
|
|
96
|
+
5. On the plan screen press `k`, then `t`, and confirm. Expect each TUI agent's
|
|
97
|
+
terminal closed and the next plan showing them offline; with a hub of 0.12.22
|
|
98
|
+
or newer, `h` stops the headless agents the same way.
|
|
99
|
+
|
|
100
|
+
Record the versions, the screens' text and what differed.
|
|
101
|
+
|
|
5
102
|
## ahub reset on a scratch project (#214)
|
|
6
103
|
|
|
7
104
|
Not yet run. A person runs it in a terminal, on a scratch project only, never
|
|
@@ -405,14 +502,14 @@ In the project directory, one terminal each:
|
|
|
405
502
|
3. `ahub codex`. The TUI opens; after its first thread starts, `ahub status` shows `codex idle`.
|
|
406
503
|
4. `ahub claude`. `ahub status` shows `claude idle`.
|
|
407
504
|
5. `ahub say "Each of you: reply with your name and nothing else."` Expect three replies in `ahub tail`, each within its turn time, and each agent seeing the others' replies framed as untrusted (`<channel source="agent-hub">` in Claude, `[agent-hub message from ...]` in Codex and Kimi).
|
|
408
|
-
6. While Codex is mid-turn on a long prompt typed in its TUI, run `ahub say @codex "status?"`. `ahub status` shows `
|
|
505
|
+
6. While Codex is mid-turn on a long prompt typed in its TUI, run `ahub say @codex "status?"`. `ahub status` shows `1` in the codex row's `Q` column; the message is injected after the turn completes.
|
|
409
506
|
7. `ahub say @kimi one`, `ahub say @kimi two` back to back: both answered in order, none lost.
|
|
410
507
|
8. Ask Kimi for something that needs a tool (`ahub say @kimi "create /tmp/agenthub-smoke.txt"`): `ahub tail` prints the permission request; `ahub permit <id> <option>` answers it; no answer within 120 s cancels it.
|
|
411
508
|
9. `ahub kill`: no `kimi acp` or `codex app-server` process survives (`pgrep -fl "kimi acp|codex app-server"`).
|
|
412
509
|
|
|
413
510
|
## M2: tiers, digests, steer, recall
|
|
414
511
|
|
|
415
|
-
1. `ahub pause kimi`, then `ahub say @kimi "[STATUS] one"`, `ahub say @kimi "[STATUS] two"`, `ahub say @kimi "how many agent-hub messages are in this prompt?"`. `ahub status` shows `
|
|
512
|
+
1. `ahub pause kimi`, then `ahub say @kimi "[STATUS] one"`, `ahub say @kimi "[STATUS] two"`, `ahub say @kimi "how many agent-hub messages are in this prompt?"`. `ahub status` shows `3` in Kimi's `Q` column and a `paused` detail. `ahub resume kimi`: one Kimi turn answers for all of them, and on the first delivery of the hub run it also reports the `hub` memory item (`grep recall .agenthub/state/hub.log` shows its size).
|
|
416
513
|
2. `ahub say "[FYI] note"` appears on `ahub tail` as `[fyi: record only]` and no peer goes busy.
|
|
417
514
|
3. With Codex mid-turn on a long prompt typed in its TUI: `ahub say @codex "[IMPORTANT] stop and summarize"`. The running turn changes course (steer) instead of a new turn starting afterwards; `ahub status` never shows it queued. `ahub say @codex "[STATUS] later"` during the same turn stays queued until the turn ends.
|
|
418
515
|
4. Two agents chatting without markers: their replies reach the third agent as digests, not one turn per message.
|
|
@@ -422,8 +519,8 @@ In the project directory, one terminal each:
|
|
|
422
519
|
Needs a model gateway in `omniroute.urls` (for the owner: the campus gateway over VPN, or the Access-protected public URL with its two header files) and a key: `OMNIROUTE_API_KEY`, or `omniroute.api_key_file` in `.agenthub/config.json`.
|
|
423
520
|
|
|
424
521
|
1. `ahub doctor`: `omniroute` healthy, `omniroute key` present, `switchyard` installed or not.
|
|
425
|
-
2. In a scratch git repo: `ahub up`, `ahub local`, `ahub tail`, then `ahub say @local "fix the typos in <file>, run git diff --stat and report"`. `ahub tail` shows a permission request for the edit; `ahub permit <id> allow`. The file changes, the answer is a short conclusion, `ahub status` shows `
|
|
426
|
-
3. Same with `AGENTHUB_SWITCHYARD_BIN` (or `switchyard-server` on PATH) set before `ahub up`: `ahub status` shows `
|
|
522
|
+
2. In a scratch git repo: `ahub up`, `ahub local`, `ahub tail`, then `ahub say @local "fix the typos in <file>, run git diff --stat and report"`. `ahub tail` shows a permission request for the edit; `ahub permit <id> allow`. The file changes, the answer is a short conclusion, `ahub status` shows the served model in the local row's `MODEL` column and `vllm` in the backend table's `PROVIDER` column.
|
|
523
|
+
3. Same with `AGENTHUB_SWITCHYARD_BIN` (or `switchyard-server` on PATH) set before `ahub up`: `ahub status` shows the served model in `MODEL`, `switchyard` in the backend's `PROVIDER` column, and a `switchyard` detail with `127.0.0.1:<port>`; `lsof -nP -iTCP -sTCP:LISTEN | grep switchy` shows loopback only; `.agenthub/state/switchyard.toml` is mode 600 and holds no key; after `ahub kill` the file and the process are gone.
|
|
427
524
|
4. claude-mem: `sqlite3 -readonly ~/.claude-mem/claude-mem.db "select agent_id, agent_type, project, title from observations order by id desc limit 3"` shows `local | local-worker` rows a minute or two later (claude-mem's observer runs asynchronously).
|
|
428
525
|
|
|
429
526
|
## M4: task board
|
|
@@ -437,7 +534,7 @@ Needs a model gateway in `omniroute.urls` (for the owner: the campus gateway ove
|
|
|
437
534
|
## M5: budget relay
|
|
438
535
|
|
|
439
536
|
1. `ahub up`, `ahub kimi`, `ahub local`, `ahub tail`. Give Kimi a task, then `ahub budget set kimi 0.95 --resets-in 2m`.
|
|
440
|
-
2. `ahub tail`: Kimi is asked for a checkpoint, writes `.agenthub/checkpoint.md`, calls `hub_checkpoint`; then `budget: kimi paused ...; checkpoint received` and `budget: moved from kimi: #<id> owner -> local`. `ahub status` shows `
|
|
537
|
+
2. `ahub tail`: Kimi is asked for a checkpoint, writes `.agenthub/checkpoint.md`, calls `hub_checkpoint`; then `budget: kimi paused ...; checkpoint received` and `budget: moved from kimi: #<id> owner -> local`. `ahub status` shows a `paused` detail for Kimi naming the budget reason, `ahub board` shows the task with `local`.
|
|
441
538
|
3. `ahub budget set kimi 0.97` again changes nothing. `ahub resume kimi` is refused while the record is open; `ahub budget resume kimi` overrides it, and further readings over the gate do not pause Kimi again until that window has reset.
|
|
442
539
|
4. `ahub budget set kimi 0.1` (the mocked reset), or wait for the reset time: `kimi resumed`, and Kimi gets one envelope listing what moved.
|
|
443
540
|
5. Codex: with a TUI attached through `ahub codex`, `ahub budget` shows its windows from `account/rateLimits/read`; on a limited account Codex is paused until `resetsAt` without a checkpoint.
|
|
@@ -492,6 +589,7 @@ Needs a model gateway in `omniroute.urls` (for the owner: the campus gateway ove
|
|
|
492
589
|
| 2026-09-20 | #40-#43 after the review fixes, live hub from the PR branch (Kimi 2.0.1, Pi 0.86.0 MLX) | pass: say/remember refuse an absorbed flag and send it after `--`; paused peer printed `queued 3 (2 important)`; Pi mlx->auto handover emitted one `state pi -> idle` and no offline; unprimed Kimi answered a no-action note as `fyi` (`NOT DELIVERED(fyi)`) once the shared instruction named the markers |
|
|
493
590
|
| 2026-09-20 | status.json queue depth, live hub | found: the file was one message behind (`queued 3` vs live 4) and kept `queued 4` on an empty queue, because `publish` emits before it enqueues and a delivery emits nothing. Fixed: `bus.onQueues` -> `writeStatus`; re-measured 1 -> 2 -> 3 -> 0 exact, field absent at 0 |
|
|
494
591
|
| 2026-09-20 | 0.6.3 gate + isolated install | pass: `bun run check` 301 tests / 1600 expect / `check: OK`; npm pack tgz (281 KB) → isolated BUN_INSTALL prefix, `ahub --version` 0.6.3, `ahub init` wrote all five files; direct `bun add -g .` of the workspace root fails (nameless dependency, DependencyLoop) |
|
|
592
|
+
| 2026-10-10 | #285 live item 1: Kimi 2.1.1 sends `usage_update` in a hub ACP session, and when | unverified: the running hub records nothing about the notification (the gap phase 1 closes), so observing it means starting a Kimi turn with a stream capture, which this phase did not do. The evidence so far: the 2.0.1 capture above (2026-09-19 row, before the adapter's busy gate existed) and the 2.1.1 source text (the notification's own comment names `used` the context token count and `size` the bound model's maximum, emitted after the turn settles). The adapter now accepts the update whenever it names the current session, idle included, so the unverified order cannot drop it |
|
|
495
593
|
|
|
496
594
|
## Issue #7 verification ledger (2026-09-19)
|
|
497
595
|
|
|
@@ -1405,8 +1503,11 @@ The historical Claude benign checks above used the earlier hook policy. The curr
|
|
|
1405
1503
|
|
|
1406
1504
|
Unverified natively (Codex 0.162.0): whether a thread that got a `never` or `on-request` override keeps it when the
|
|
1407
1505
|
same thread is resumed through the hub's proxy by a hand-run `codex --remote` after `ahub permission codex ask` was
|
|
1408
|
-
given while no TUI was attached. The
|
|
1409
|
-
|
|
1506
|
+
given while no TUI was attached. The status says ask; the current proxy keeps restoration owed per thread and
|
|
1507
|
+
sends the captured native policy once on the resumed thread's next accepted turn, including when the TUI echoes
|
|
1508
|
+
the resume response's sticky override. This protects only the restoring turn; a repeat on the second TUI turn
|
|
1509
|
+
cannot be distinguished from an explicit choice and passes through. Both turns' TUI traffic remain natively unverified. `ahub codex` always starts
|
|
1510
|
+
a new proxy, which is not affected. See the current #270 manual checklist above.
|
|
1410
1511
|
|
|
1411
1512
|
## Whole-board task progress and dashboard themes (#246)
|
|
1412
1513
|
|
|
@@ -1422,3 +1523,28 @@ with the dashboard. In the dashboard select Light, Dark and System, reload each
|
|
|
1422
1523
|
choice, change OS theme on System, and check text, bars, stage labels and controls.
|
|
1423
1524
|
Repeat with cookies blocked and across two dashboard ports; verify unchanged
|
|
1424
1525
|
progress snapshots do not repeatedly announce the live region. Record the exact head and verdict here.
|
|
1526
|
+
|
|
1527
|
+
## One-shot command output (#284 phase 1)
|
|
1528
|
+
|
|
1529
|
+
Human visual inspection: **not run**. Automated geometry and color checks are separate evidence.
|
|
1530
|
+
At the PR head, run `ahub status`, `ahub board`, `ahub budget` and `ahub doctor` in a light and a dark terminal,
|
|
1531
|
+
at 80 and 120 columns. Inspect Korean and long task titles, settlement and hold details, modes and quota windows.
|
|
1532
|
+
Confirm readable state/level words with `--color=never`, matching visible text with `--color=always`, and no cut fields.
|
|
1533
|
+
Compare status's shortened informational ids with `status --full`; suggested command ids must always be whole. In `ahub console`,
|
|
1534
|
+
run `status`, `board` and `budget` and confirm their rows stay within the four-column indent and output remains plain. Run `ahub doctor` in the terminal outside the console.
|
|
1535
|
+
Record the exact head and visual verdict here after the owner performs this leg.
|
|
1536
|
+
|
|
1537
|
+
## ACP cancellation settlement (#304)
|
|
1538
|
+
|
|
1539
|
+
Not run: real Qwen and Kimi cancellation timing remains owed and does not gate the adapter fix, per issue #304. In an operator-approved ACP session for each provider, record the native version, observe prompt activity, send `session/cancel`, and measure until the correlated prompt result. Record what a new prompt receives before that result. Distinguish those measurements from the fake's ordered barrier and cancellation acknowledgement; do not spend a native turn solely to complete this implementation.
|
|
1540
|
+
## Remaining command output (#284 phases 2 and 3)
|
|
1541
|
+
|
|
1542
|
+
Human visual inspection: **not run**. At the PR head, use light and dark terminals at 80 and 120 columns.
|
|
1543
|
+
Run `ahub projects`, `ahub status --all`, `ahub queue list`, `ahub turns`, `ahub doctor --orphans`,
|
|
1544
|
+
`ahub queue show <id>`, `ahub models status`, `ahub budget execution status`, `ahub report` and `ahub report --by task`.
|
|
1545
|
+
Inspect long Korean paths, all changed filenames, public private-delivery stubs, nested labelled fields, relative times and report coverage sentences.
|
|
1546
|
+
Compare `--color=never` with `--color=always`. Confirm turn, queue, project and execution budget ids are whole without `--full` and can be copied into their commands.
|
|
1547
|
+
In the console run `status --all`, `queue list`, `queue show <id>`, `turns`, `budget execution status`, `report` and `report --by task`;
|
|
1548
|
+
run `ahub projects`, `ahub doctor --orphans` and `ahub models status` in the terminal outside the console. Status's informational ids can be compared with `status --full`.
|
|
1549
|
+
Confirm JSON output stays plain under `--color=always`. Record the exact head and visual verdict here after the owner performs this leg.
|
|
1550
|
+
|