@staix/agent-hub 0.12.44 → 0.12.45
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 +22 -0
- package/README.md +3 -3
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/docs/adding-an-ai-tool.md +102 -14
- package/docs/agent-notes/README.md +9 -0
- package/docs/agent-notes/adapters.md +58 -44
- package/docs/agent-notes/budget.md +1 -1
- package/docs/agent-notes/daemon.md +9 -7
- package/docs/agent-notes/local-worker.md +4 -3
- package/docs/agent-notes/tasks.md +8 -5
- package/docs/agent-notes/tests.md +7 -7
- package/docs/operations.md +99 -7
- package/docs/quickstart.md +5 -1
- package/docs/sdlc.md +26 -8
- package/docs/security.md +6 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +114 -5
- package/docs/verification/2026-10-21-469-r1-dashboard-tokens.md +43 -0
- package/docs/verification/2026-10-21-469-r2-dashboard-shell.md +54 -0
- package/docs/verification/2026-10-21-469-r3-dashboard-board.md +51 -0
- package/docs/verification/2026-10-21-469-r4-dashboard-views.md +77 -0
- package/docs/verification/2026-10-21-469-r6-dashboard-motion-states.md +82 -0
- package/docs/verification/2026-10-22-0.12.45.md +60 -0
- package/docs/verification/assets/469-r1-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r1-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r1-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r1-board-390-light.png +0 -0
- package/docs/verification/assets/469-r2-agents-1440-dark.png +0 -0
- package/docs/verification/assets/469-r2-agents-768-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-1024-light.png +0 -0
- package/docs/verification/assets/469-r2-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r2-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-390-light.png +0 -0
- package/docs/verification/assets/469-r2-board-768-light.png +0 -0
- package/docs/verification/assets/469-r3-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r3-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-board-390-light.png +0 -0
- package/docs/verification/assets/469-r3-detail-1440-dark.png +0 -0
- package/docs/verification/assets/469-r3-detail-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-detail-390-dark.png +0 -0
- package/docs/verification/assets/469-r3-plan-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-plan-390-light.png +0 -0
- package/docs/verification/assets/469-r4-agents-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-agents-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-agents-390-dark.png +0 -0
- package/docs/verification/assets/469-r4-memory-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-memory-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-memory-390-dark.png +0 -0
- package/docs/verification/assets/469-r4-message-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-office-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-office-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-settings-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-settings-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-stream-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-stream-390-light.png +0 -0
- package/docs/verification/assets/469-r6-agents-empty-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-agents-empty-390-light.png +0 -0
- package/docs/verification/assets/469-r6-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-board-empty-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-board-empty-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-skeleton-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-skeleton-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-toasts-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-toasts-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-toasts-390-light.png +0 -0
- package/docs/verified.json +19 -16
- package/package.json +5 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +9818 -9605
- package/src/adapters/adapter-api.ts +2 -0
- package/src/adapters/agy/definition.ts +3 -1
- package/src/adapters/claude-channel.ts +14 -5
- package/src/adapters/codex-appserver.ts +1 -0
- package/src/adapters/conformance/acp.ts +114 -0
- package/src/adapters/conformance/codex.ts +191 -0
- package/src/adapters/conformance/enrollment.ts +43 -0
- package/src/adapters/conformance/factories.ts +17 -0
- package/src/adapters/conformance/fake-paths.ts +12 -0
- package/src/adapters/conformance/fakes/acp-server.ts +267 -0
- package/src/adapters/conformance/fakes/acp-watchdog-agent.ts +10 -0
- package/src/adapters/conformance/fakes/app-server.ts +119 -0
- package/src/adapters/conformance/fakes/codex-bin.ts +15 -0
- package/src/adapters/conformance/fakes/jcode-agent.ts +91 -0
- package/src/adapters/conformance/fakes/model-server.ts +39 -0
- package/src/adapters/conformance/fakes/pi-rpc.ts +32 -0
- package/src/adapters/conformance/index.ts +11 -0
- package/src/adapters/conformance/kit.ts +50 -0
- package/src/adapters/conformance/local-pi.ts +100 -0
- package/src/adapters/conformance/owned-acp.ts +44 -0
- package/src/adapters/definitions/builtin-metadata.ts +23 -6
- package/src/adapters/definitions/native-cli-entry-kimi.ts +7 -1
- package/src/adapters/definitions/native-cli-entry.ts +7 -19
- package/src/adapters/definitions/native-cli-preview.ts +2 -11
- package/src/adapters/definitions/qwen.ts +4 -1
- package/src/adapters/external-metadata.ts +48 -0
- package/src/adapters/external-module-scan.ts +126 -0
- package/src/adapters/external-modules.ts +240 -0
- package/src/adapters/pi.ts +2 -0
- package/src/adapters/public.ts +7 -0
- package/src/adapters/registry-types.ts +10 -0
- package/src/adapters/registry.ts +51 -15
- package/src/cli/help.ts +21 -16
- package/src/cli/identity.ts +1 -1
- package/src/cli/main.ts +23 -5
- package/src/cli/upgrade-labels.ts +1 -1
- package/src/cli/upgrade-presentation.ts +1 -1
- package/src/cli/upgrade-restore-state.ts +3 -2
- package/src/cli/upgrade-runtime.ts +4 -1
- package/src/hub/board.ts +30 -4
- package/src/hub/config-apply.ts +18 -10
- package/src/hub/config-reload.ts +8 -5
- package/src/hub/config-trust.ts +1 -1
- package/src/hub/control-client.ts +4 -1
- package/src/hub/daemon.ts +95 -39
- package/src/hub/facts.ts +76 -13
- package/src/hub/hub-tools.ts +21 -5
- package/src/hub/instance-config.ts +17 -5
- package/src/hub/instance-execution.ts +20 -8
- package/src/hub/instances.ts +2 -1
- package/src/hub/peers.ts +9 -0
- package/src/hub/plan-conformance.ts +83 -0
- package/src/hub/review-findings.ts +104 -0
- package/src/hub/roster.ts +31 -5
- package/src/hub/sdlc-queue.ts +19 -4
- package/src/hub/sdlc-release.ts +16 -1
- package/src/hub/sdlc.ts +27 -12
- package/src/hub/settings.ts +5 -1
- package/src/hub/snapshots.ts +8 -6
- package/src/hub/tasks.ts +275 -45
- package/src/hub/ui.ts +2 -2
- package/src/ui/index.html +559 -78
- package/src/ui/office-model.ts +3 -1
- package/src/ui/peer-palette.ts +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.45
|
|
6
|
+
|
|
7
|
+
The dashboard is redesigned (#469), a machine-local, content-pinned adapter module can add an ACP agent (#451 A4), the peer lists and the CLI surface come from the adapter definitions (#625), and reviews gain structured findings with an Important gate (#481 S3) and plan conformance with a person-only departure acceptance (#481 S2).
|
|
8
|
+
|
|
9
|
+
- Load external adapter modules: `adapter_modules: [{ path, sha256 }]` in an untracked `.agenthub/config.local.json` names a self-contained ESM bundle that the daemon imports at startup from its hash-checked bytes, with `@staix/agent-hub/adapter` bound to the running hub. A module is refused on its own (bad pin, unsupported import, an import that does not settle within 2 s) while the builtins start; the CLI only sees bounded metadata. `@staix/agent-hub/adapter` and `@staix/agent-hub/conformance` are published for module authors. A pin authorizes code to run with daemon privileges; it is not a sandbox (#451 A4, #648, #653).
|
|
10
|
+
- The peer lists (`KNOWN_PEERS`, `OPTIONAL_PEERS`, `START_MODE_PEERS`, role, help and planner order) and each builtin's `ahub <kind>` entry, preview and help usage come from the adapter definitions; the orders shown are unchanged (#625, #656). The daemon reads optional peer capability members for session generation and permission mode instead of checking peer classes (#625, #667).
|
|
11
|
+
- `hub_review` and `ahub review --findings` take structured findings (id, pass, severity, disposition). In enforce, an open Important finding blocks approval and carries across later heads until a reviewer marks it fixed or false_positive or the person accepts it with a rationale; a supplied head must be the task's current head, and a finding id seen on a second task proposes one backlog card for an AGENTS.md or area-note amendment. `sdlc.max_nits` sets the nit cap (default 5) (#481 S3, #652, #657).
|
|
12
|
+
- Plan conformance: a task plan may carry `proof` and `risks`, and review, status and the merge queue compare the PR's changed files with the plan's literal file or directory entries (renames and copies read as unknown). In enforce, a departure needs an amended plan or the person's `ahub sdlc accept-departure <task> <head> <reason...>`, which binds the repository, PR, head, file inventory, plan and repository scope and expires on restart (#481 S2, #665).
|
|
13
|
+
- Review readiness keeps a lost reuse proof apart from quiet review drift, a delayed check that is refused tells its owner, and review-thread authors other than the person count as pending bot comments (#230, #641, #643, #644).
|
|
14
|
+
- A large file rewritten with the same size within one filesystem tick on Linux is now seen as changed: facts compare a recently written file by its bytes, within a bounded budget (#428, #646).
|
|
15
|
+
- When `ahub kimi --mode tui` fails because the running hub's retained `kimi_cmd` has no TUI form, the error says the command is the running hub's and that the file's value waits for `ahub settings reload` (#654, #658).
|
|
16
|
+
- The daemon's snapshot Git budget reads an injectable clock, a host-only `snapshotClock` option, so a test can hold it fixed and a slow CI disk cannot spend the 250 ms before a turn's end evidence is read (an exhausted budget records an unknown tree, and an unknown tree restarts the empty-turn stall count: the intermittent #661 failure of the #498 stall test). Nothing in the shipped daemon sets it, no configuration key or tool reaches it, and the share stays 250 ms.
|
|
17
|
+
- The dashboard board gets simpler cards (title, owner avatar, one state and priority badge, release), a task detail that opens as a right-hand sheet with its lines grouped into Status, People, Dates and Links, and a Plan sheet that holds the five board forms (plan, split, releases, delivery evidence, propose) with the fields and actions they had (#469 R3).
|
|
18
|
+
- The dashboard gets an app shell: from 1024px a sticky top bar and a left sidebar, below it a row of tabs that scrolls inside itself so the page never scrolls sideways; the connection status shows a coloured dot; keys `1`-`7` switch views, `?` lists the keys and `Esc` closes a sheet, ignored while typing; navigation icons come from an inline sprite of nine Tabler icons (MIT notice in `THIRD_PARTY_NOTICES.md`). View names, order, the cookie and ids are unchanged (#469 R2).
|
|
19
|
+
- The dashboard's style block is rewritten around one token block (colour, a 4px spacing scale, a type scale, a 6/10/full radius rule and two shadows), the dead rules are gone, the indigo accent replaces green for primary actions, focus and selection, and every peer colour has a light and a dark variant. The layout, views and behaviour are unchanged, and the contrast test now also covers the accent, dots, disabled controls and the peer palette in both themes (#469 R1).
|
|
20
|
+
- The dashboard's Agents rows become a summary line plus one folded details block, the Stream gets aligned event rows with its filters above the list, Memory rows and detail use the colour tokens, Settings groups are sections with a "restart pending" badge and Message uses the shared form styles. The star, arrow, checking and Office condition glyphs become Tabler icons and drawn shapes next to their words (three more icons, twelve in all), the Office scene's colours are checked through the same tokens in the contrast test, and a test now fails if a listed glyph comes back (#469 R4, R5).
|
|
21
|
+
- The dashboard keeps task cards, peer rows and memory rows per id across a poll, so hover, an open fold and a focused card survive an update that changed something else. It gets quiet motion on `transform` and `opacity` only (press, view fade, card arrival, toast, sheets) that is off under reduced motion, first-load skeletons for the board and the peers, empty views that name their command, a disconnected state with the time of the last snapshot, and notices as an `aria-live` toast stack where errors stay until dismissed and a success fades after 4 s (#469 R6).
|
|
22
|
+
- A board card's one badge now also says when the task is overdue or its check is running ("in progress P2, overdue, check running"), in its text and in the card's accessible name, instead of a second and third badge. An overdue card turns the badge warn unless the state is already an error, and the task detail keeps its own Overdue and Check running lines (#293).
|
|
23
|
+
- The dashboard keeps its first-load skeletons for the board and the peers after a project switch until that project's first snapshot renders (a slow or failed first snapshot no longer reads as "No project selected", which is now only for no running project), and a toast that is dismissed, expires or is pushed out no longer takes keyboard focus from another toast's Dismiss button (#469 R6 follow-ups).
|
|
24
|
+
- Four #450 I2 follow-ups: the interactive recovery screen and `--verbose` receipt now show a restored extra's recorded outcome after its declaration was revoked instead of throwing "invalid restore receipt"; a roster key left by an undeclared instance (`codex-2`) loads as inert and `ahub roster remove codex-2` deletes it, while any other unknown key is still an unsupported peer (only a kind that can declare an instance counts: `roster.jcode-worker` stays a typo). A key that lives only in the shared `.agenthub/config.json` is refused with "edit .agenthub/config.json" instead of reporting a removal that changes nothing, and the removal says "stale roster key removed" instead of a roster state; the `peer_instances` refusal text and the spec say it applies only while a declared instance may be live, and ask to end the extra sessions before removing a declaration; and the git calls behind an extra-instance start share one 5 s allowance instead of 5 s each (#225).
|
|
25
|
+
- A Claude channel tool result no longer tells a session to call `hub_inbox` while a delivery hold refuses its pulls (the counts and held rows stay), and a `hub_inbox` result states the queued count once (#221). Documented limit: mid-turn steer of a push-capable Claude session was proven only on Claude Code 2.1.302.
|
|
26
|
+
|
|
5
27
|
## 0.12.44
|
|
6
28
|
|
|
7
29
|
A standard ACP agent can join a project from config alone (#451 A3), adapter definitions become the single dispatch point (#451 A2b), agy stops reading Claude's state files (#624), and owner turns that write nothing are flagged (#498).
|
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.
|
|
8
|
+
Status: 0.12.45, 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
|
|
@@ -88,14 +88,14 @@ keeps headless startup. On 0.12.39 the TUI launcher stopped after the first hub
|
|
|
88
88
|
0.12.40 opens it again (#564). On 0.12.40 `ahub codex` in a recorded Orca terminal
|
|
89
89
|
failed with `invalid remote address`; 0.12.41 starts it again (#586). Since 0.12.42
|
|
90
90
|
`ahub agy` accepts the Antigravity CLI 1.4.x line (1.4.2 verified; other patches warn),
|
|
91
|
-
because agy updates itself and 0.12.41 refused 1.4.3 (#589). Since 0.12.43 agy's native reads are recorded as peer facts on 1.4.2 and 1.4.3 (#555); 1.4.3 stays unverified, and in its TUI the hub tools appear from the second turn (#267, #607). Since 0.12.44 a headless Kimi or Qwen start made by the hub also opens `ahub watch <peer>` through the terminal provider (#286), an owner turn streak with no recorded writes raises a planner notice (`task_sweep.no_writes_turns`, default 2, 0 disables; #498), a declared agy extra instance is refused at config load (#625), and a standard ACP agent can join a project as a config-only adapter declared in untracked `.agenthub/config.local.json`: headless, off roster by default and pinned to its native identity (#451).
|
|
91
|
+
because agy updates itself and 0.12.41 refused 1.4.3 (#589). Since 0.12.43 agy's native reads are recorded as peer facts on 1.4.2 and 1.4.3 (#555); 1.4.3 stays unverified, and in its TUI the hub tools appear from the second turn (#267, #607). Since 0.12.44 a headless Kimi or Qwen start made by the hub also opens `ahub watch <peer>` through the terminal provider (#286), an owner turn streak with no recorded writes raises a planner notice (`task_sweep.no_writes_turns`, default 2, 0 disables; #498), a declared agy extra instance is refused at config load (#625), and a standard ACP agent can join a project as a config-only adapter declared in untracked `.agenthub/config.local.json`: headless, off roster by default and pinned to its native identity (#451). Since 0.12.45 the dashboard has a sidebar or tabs, view keys `1`-`7`, a task side sheet, a Plan sheet for the board forms and loading, empty and disconnected states (#469); a person can add a bundled ACP adapter module, pinned by `sha256` in untracked `.agenthub/config.local.json` and imported only by the daemon at startup, and module CI gets the `@staix/agent-hub/conformance` kit (#451); reviews take structured findings, and in SDLC enforce an open Important finding or a changed file outside the plan refuses approval until fixed, amended or accepted by the person with `ahub sdlc accept-departure` (#481); and `ahub roster remove` deletes the roster key of an instance no longer declared (#225).
|
|
92
92
|
Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
|
|
93
93
|
refuse while cancelled rows exist. Live-test delegated CLI commands need a daemon
|
|
94
94
|
advertising `cli_audit_delegated` and refuse with an upgrade remedy otherwise
|
|
95
95
|
(#410). For local/Pi implementation, opt in to an available
|
|
96
96
|
`checks.runner = "container"`; issue-backed owners also need authenticated
|
|
97
97
|
spec access. PII work and designated reviewers retain their separate exemptions.
|
|
98
|
-
See [release verification](docs/verification/2026-10-
|
|
98
|
+
See [release verification](docs/verification/2026-10-22-0.12.45.md) for the live legs
|
|
99
99
|
still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
|
|
100
100
|
real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
|
|
101
101
|
(#478), a live headless Claude review (#223), a Claude session over 32 MiB (#517), a
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -17,3 +17,32 @@ Unless required by applicable law or agreed to in writing, software distributed
|
|
|
17
17
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
18
18
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
19
19
|
specific language governing permissions and limitations under the License.
|
|
20
|
+
|
|
21
|
+
## Tabler Icons
|
|
22
|
+
|
|
23
|
+
The inline icon sprite in `src/ui/index.html` (the dashboard's navigation, help, close, star, arrow and checking icons) contains the SVG
|
|
24
|
+
path data of twelve icons from Tabler Icons (outline set, 24x24, stroke 2), copied unchanged from https://github.com/tabler/tabler-icons:
|
|
25
|
+
`layout-kanban`, `building-store`, `activity`, `users`, `message`, `brain`, `settings`, `help`, `x`, `star`, `arrow-right` and
|
|
26
|
+
`refresh`. No Tabler package is installed or bundled.
|
|
27
|
+
|
|
28
|
+
MIT License
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2020-2026 Paweł Kuna
|
|
31
|
+
|
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
33
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
34
|
+
in the Software without restriction, including without limitation the rights
|
|
35
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
36
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
37
|
+
furnished to do so, subject to the following conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice shall be included in all
|
|
40
|
+
copies or substantial portions of the Software.
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
43
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
44
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
45
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
46
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
47
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
48
|
+
SOFTWARE.
|
|
@@ -10,7 +10,7 @@ Under **Adapter API 1** (issue #451), every tool is integrated as an `AdapterDef
|
|
|
10
10
|
|
|
11
11
|
1. **One Definition per Kind**: Each tool is identified by a unique `kind` (e.g., `codex`, `kimi`, `agy`, `claude`, `jcode`, `pi`, `local`, `qwen`). Its capabilities, transport, launch argv, permission mappings, and recovery contracts are encapsulated in an `AdapterDefinition`.
|
|
12
12
|
2. **Core Independence**: Core daemon logic (`src/hub/daemon.ts`), task management (`src/hub/tasks.ts`), and routing (`src/hub/routing.ts`) inspect definition fields (such as `transport`, `identity`, `attach`, `budget`), never raw vendor names.
|
|
13
|
-
3. **Strict Conformance**: Every builtin adapter must run the conformance test kit (`test/conformance/`). Unsupported legs must provide explicit, documented reasons rather than silent test skips. (
|
|
13
|
+
3. **Strict Conformance**: Every builtin adapter must run the conformance test kit (`test/conformance/`). Unsupported legs must provide explicit, documented reasons rather than silent test skips. (A4 exports the same callable contracts for external package CI.)
|
|
14
14
|
4. **Trust & Security Boundaries**:
|
|
15
15
|
- **On-prem vs. Cloud**: `trust.onPrem` controls whether private/PII tasks may be routed to the agent. A cloud agent (`trust.onPrem: false`) never receives PII task text or memory entries.
|
|
16
16
|
- **Path Protection**: Any workspace configuration directory owned by the agent (e.g., `.agents/`, `.codex/`) must be listed in `AGENT_CONFIG_SEGMENTS` (`src/hub/permission-mode.ts`) so agents cannot self-modify their own hooks or plugins.
|
|
@@ -24,7 +24,7 @@ agent-hub supports five transport architectures (`transport` in `AdapterDefiniti
|
|
|
24
24
|
|
|
25
25
|
| Transport | Description | Best Suited For | Examples |
|
|
26
26
|
| :--- | :--- | :--- | :--- |
|
|
27
|
-
| **`acp`** | Agent Client Protocol over stdio JSON-RPC | Agents implementing the standard ACP specification | Kimi, Qwen |
|
|
27
|
+
| **`acp`** | Agent Client Protocol over stdio JSON-RPC | Agents implementing the standard ACP specification | Kimi, Qwen, Jcode |
|
|
28
28
|
| **`codex-appserver`** | Bidirectional JSON-RPC with proxy intercept and thread tracking | Tools with structured thread resume and proxy-injectable RPC | Codex |
|
|
29
29
|
| **`claude-channel`** | MCP plugin running inside the host agent with WebSocket control link | Agents with native plugin systems that push notifications | Claude Code |
|
|
30
30
|
| **`ws-pull`** | Workspace MCP plugin connecting to daemon WebSocket; pull-only message inbox | Agents with local plugin discovery and interactive TUIs | Google Antigravity (`agy`) |
|
|
@@ -65,9 +65,11 @@ Defines how the hub MCP server identity reaches the tool:
|
|
|
65
65
|
|
|
66
66
|
#### `permission`
|
|
67
67
|
Maps hub permission modes (`ask`, `ask-when-needed`, `never-ask`) to native flags or ACP mode IDs:
|
|
68
|
-
* `grants`:
|
|
69
|
-
* `
|
|
70
|
-
* `
|
|
68
|
+
* `grants`: the text that says what each hub mode does for this kind (`ahub permission` and the settings preview show it). It is a description, not the native flag: the native mode id or policy is applied by the adapter (an ACP mode id, Codex's `approvalPolicy`).
|
|
69
|
+
* `genericAcp`: for a configured ACP kind, the validated `modes` (hub mode to ACP mode id) and `readOnly` id that the runtime and the instance ceiling both read.
|
|
70
|
+
* `unmanaged`, `unmanagedNote`, `unmanagedStatusNote`: why the hub cannot manage native permissions, when it cannot.
|
|
71
|
+
* `unmanagedForms`: start forms where permissions remain unmanaged (e.g. `["tui"]`).
|
|
72
|
+
* `trackUnattended`: the daemon records whether a start was `--unattended` and refuses a second start with a different setting while one is in progress (Codex).
|
|
71
73
|
|
|
72
74
|
#### `session`
|
|
73
75
|
Controls session restoration, thread continuity, and crash recovery:
|
|
@@ -76,6 +78,18 @@ Controls session restoration, thread continuity, and crash recovery:
|
|
|
76
78
|
* `resumeArgv(binding, fresh)`: Builds CLI arguments to resume a persisted session.
|
|
77
79
|
* `transcriptPath(binding)`: Resolves local transcript file path for audit/usage tracking.
|
|
78
80
|
|
|
81
|
+
#### `projection`
|
|
82
|
+
Where the kind sits in the host's derived lists: `roleRank`, `startRank`, `helpRank` and `plannerEligible`. `KNOWN_PEERS`, `OPTIONAL_PEERS`, `START_MODE_PEERS`, `ROLE_ORDER`, `HELP_ORDER` and `PLANNER_PEERS` are derived from `ADAPTERS` and these ranks (`deriveAdapterLists`). The only hand-kept lists are the cross-release copies in `src/hub/peer-catalog.ts` (`KNOWN_PEERS`, `OPTIONAL_PEERS`) and `src/hub/start-mode.ts` (`START_MODE_PEERS`, for a dual-form kind): a new native kind is added there too, and `test/registry-guard.test.ts` fails until those copies equal the derived lists. A configured or external definition cannot set `projection`.
|
|
83
|
+
|
|
84
|
+
#### `cli`
|
|
85
|
+
The `ahub <kind>` surface: `command`, `startWords(form)`, and for a fixed builtin `entry` (the launcher and whether it needs lifecycle admission), `preview`, `usage` (help lines) and `launchConfigKeys`. `builtinCliEntries(definitions)` projects the entries; no configuration or module can register one.
|
|
86
|
+
|
|
87
|
+
#### `instances` and `attach`
|
|
88
|
+
`instances.mode` is `headless` (needs a headless `start.create` factory) or `external` (a channel instance started from the person's terminal). A kind with no `instances` policy (Jcode) takes no extra instance, and `validateInstances` refuses a declaration of one. `attach` holds the pull-only forms, external-hello admission and the refusal texts for a TUI that attaches without a driven peer.
|
|
89
|
+
|
|
90
|
+
#### Peer capability members
|
|
91
|
+
The runtime peer (`PeerAdapter`, `src/hub/peers.ts`) exposes optional members that the daemon reads instead of checking its class (#625 phase 6): `sessionGeneration` (native session identity: absent keeps the owner-token fallback, present but undefined is unknown and refuses bounded checks), `checkpointUsesSessionGeneration` (opt out of generation-fenced checkpoints, as Pi does), `permissionModeState` (the effective native approval state, or `unknown` / `unmanaged`) and `setPermissionMode(mode)` (resolves only after the native side acknowledges). Native safety proofs (Codex proxy and restoration, Claude launch generation) stay in the daemon.
|
|
92
|
+
|
|
79
93
|
#### `budget`
|
|
80
94
|
Defines quota and context window consumption readers:
|
|
81
95
|
* `account`: Account identifier for quota grouping.
|
|
@@ -94,20 +108,94 @@ Grok declarations require explicit `--no-leader`, since native defaults can inhe
|
|
|
94
108
|
|
|
95
109
|
---
|
|
96
110
|
|
|
97
|
-
## 5.
|
|
111
|
+
## 5. External Adapter Modules (#451 A4)
|
|
112
|
+
|
|
113
|
+
A person can authorize a bundled adapter in an untracked
|
|
114
|
+
`.agenthub/config.local.json`:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"adapter_modules": [
|
|
119
|
+
{ "path": ".agenthub/adapters/my-tool.mjs", "sha256": "<64 hex digits from the exact bundle>" }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The daemon imports the module at startup. Settings, reload, previews and ordinary
|
|
125
|
+
CLI reads never import it. A changed declaration requires restart; it cannot be
|
|
126
|
+
written through a hub tool or Settings. The CLI sees only the running daemon's
|
|
127
|
+
bounded kind and display name. Tracked/shared/cloned configuration cannot enable
|
|
128
|
+
code. A wrong hash, API mismatch, builtin collision, symlink component, path
|
|
129
|
+
outside the project, nonregular/world-writable file, oversized file or unsupported
|
|
130
|
+
module construct refuses loading without exposing source text in the error.
|
|
131
|
+
|
|
132
|
+
Publish a single self-contained ESM JavaScript bundle. Bundle third-party
|
|
133
|
+
implementation into that file; do not add runtime dependencies to the hub.
|
|
134
|
+
Runtime imports may use Node/Bun builtins except `bun:ffi` and `bun:test`, plus
|
|
135
|
+
`@staix/agent-hub/adapter`, bound to the running daemon's own SDK rather than
|
|
136
|
+
project packages. `/conformance` is available to module CI, not runtime modules.
|
|
137
|
+
Relative or other package imports, dynamic imports, require (including
|
|
138
|
+
`import.meta.require`), eval and Function-based loading
|
|
139
|
+
are refused. The scanner also refuses any `require`, `eval` or `Function` word token, including non-loading uses such as `o.require` or `instanceof Function`. String literal contents are not word tokens. The scan is conservative, not a sandbox. Authorized module code runs
|
|
140
|
+
with daemon privileges; inspect the full bundle before recording its hash. Each import must settle within two seconds or that module is refused while startup continues. This deadline stops waiting; it does not cancel module code or undo its side effects.
|
|
141
|
+
Dependencies must be ESM-only. The loader verifies the captured original bytes,
|
|
142
|
+
rewrites only scanned SDK imports to host-owned file URLs, and imports that result
|
|
143
|
+
rather than reopening a mutable source path after the hash check. A refused
|
|
144
|
+
module appears in bounded status diagnostics while other adapters keep working.
|
|
145
|
+
Its roster row stays inert; CLI config reads also retain inert unknown rows when
|
|
146
|
+
trusted module declarations exist but daemon metadata is unavailable.
|
|
147
|
+
|
|
148
|
+
The module exports `definition` with `api: ADAPTER_API`. Import the version,
|
|
149
|
+
`AcpPeer`, `BasePeer` and definition/context types from
|
|
150
|
+
`@staix/agent-hub/adapter`. The initial executable subset uses a headless ACP
|
|
151
|
+
factory, its own kind/family, `optional: true`, `defaultOnRoster: false` and
|
|
152
|
+
`trust: { onPrem: false, hubSandboxed: false }`. Preserve the host's factory
|
|
153
|
+
options and instance id when constructing the transport. Supply a launch plan,
|
|
154
|
+
start/identity/session/CLI/display metadata and only supported native mode
|
|
155
|
+
mappings; no mapping means unmanaged. By default the host disables native
|
|
156
|
+
hub-tool title auto-approval for external kinds. A cooperating module must preserve
|
|
157
|
+
that factory option; approved module code runs with daemon privileges, so this is
|
|
158
|
+
a host default rather than isolation from a module that overrides its options. The loader refuses fixed-builtin capabilities and unsupported
|
|
159
|
+
fields rather than silently ignoring them. See `validateExternalDefinition` for
|
|
160
|
+
the exact accepted subset.
|
|
161
|
+
|
|
162
|
+
External kind instances are explicitly unsupported in A4. Follow-up board task
|
|
163
|
+
#287 owns execution roots, CLI identity and recovery for them. Broadening the
|
|
164
|
+
module transport beyond the existing headless ACP host port also needs a reviewed
|
|
165
|
+
host contract (#625 phase 6); selecting a different native engine is not a generic extension.
|
|
166
|
+
|
|
167
|
+
For example, bundle an ESM definition with its dependencies and record its digest:
|
|
98
168
|
|
|
99
|
-
|
|
169
|
+
```bash
|
|
170
|
+
bun build ./adapter.ts --target bun --format esm --external @staix/agent-hub/adapter --outfile .agenthub/adapters/my-tool.mjs
|
|
171
|
+
shasum -a 256 .agenthub/adapters/my-tool.mjs
|
|
172
|
+
```
|
|
100
173
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
* **Conformance Requirement**: External modules must pass the conformance kit before admission.
|
|
174
|
+
Enable its roster row only after loading it and exercising the applicable
|
|
175
|
+
conformance contracts. Source and target recovery runtimes must advertise
|
|
176
|
+
`external_adapter_modules`; old runtimes cannot preserve the verified catalog.
|
|
105
177
|
|
|
106
178
|
---
|
|
107
179
|
|
|
108
180
|
## 6. The Conformance Kit
|
|
109
181
|
|
|
110
|
-
The
|
|
182
|
+
The exported Bun kit (`@staix/agent-hub/conformance`) supplies callable transport
|
|
183
|
+
registrations, `createFromDefinition`, `registerConformance`, explicit unsupported
|
|
184
|
+
leg validation and immutable `fakePaths`. Importing it starts no fake transport;
|
|
185
|
+
call a registration in your own Bun test file. The hub's `test/conformance/`
|
|
186
|
+
wrappers run the same implementation against every applicable builtin.
|
|
187
|
+
|
|
188
|
+
An external adapter test imports its definition and calls
|
|
189
|
+
`registerConformance` with its real factory and a fake transport fixture. ACP
|
|
190
|
+
adapters also call `registerAcpConformance({ definition, ... })`; unsupported native
|
|
191
|
+
approval, mode or quota legs require concrete reasons, never silent skips. Other
|
|
192
|
+
exports cover Codex, owned ACP, Pi, local and external-channel fixture contracts.
|
|
193
|
+
Those exports do not grant an external runtime access to a builtin-only engine.
|
|
194
|
+
The shipped fake assets live under `src/adapters/conformance/fakes`, so package CI needs
|
|
195
|
+
no development-tree files. Native account/TUI probes remain separate from these
|
|
196
|
+
synthetic protocol contracts.
|
|
197
|
+
|
|
198
|
+
The kit validates that an adapter satisfies hub invariants:
|
|
111
199
|
|
|
112
200
|
```bash
|
|
113
201
|
# Run the complete conformance suite
|
|
@@ -120,7 +208,7 @@ bun test test/conformance/agy.test.ts
|
|
|
120
208
|
|
|
121
209
|
### Invariants Verified by the Kit:
|
|
122
210
|
1. **Enrollment & Coherence (`enrollment.test.ts`)**: Every definition in `ADAPTERS` and reviewer variant must be enrolled. A kind without kit coverage needs a reason in `UNSUPPORTED` (`test/conformance/enrollment.ts`), and an ACP kind that skips one of the shared `ACP_LEGS` needs a reason per leg in `UNSUPPORTED_LEGS`.
|
|
123
|
-
2. **Delivery Occupancy**: Sending messages updates driven peer state to `busy` synchronously, delivering digests reliably.
|
|
211
|
+
2. **Delivery Occupancy**: Sending messages updates driven peer state to `busy` synchronously, delivering digests reliably. A channel peer whose native hooks own occupancy (Claude) stays idle on insertion by design.
|
|
124
212
|
3. **Recovery Metadata Hygiene**: No prompt text or credentials leaked into `recoveryMetadata`.
|
|
125
213
|
4. **Watchdog Cancellation & Generation Fencing**: Expiry of the turn watchdog cancels prompts cleanly and generation fences prevent stale late answers from publishing.
|
|
126
214
|
5. **Teardown & Cleanup**: When an adapter is stopped (`peer.stop()`), all owned child processes in the process group stop with it (the peer reaches offline).
|
|
@@ -132,7 +220,7 @@ bun test test/conformance/agy.test.ts
|
|
|
132
220
|
When adding or updating an adapter, verify each item before opening a PR:
|
|
133
221
|
|
|
134
222
|
- [ ] **T0 Receipt Recorded**: Document native CLI version, observed hook payloads, and protocol outputs in `docs/verification/YYYY-MM-DD-<kind>-t0.md`.
|
|
135
|
-
- [ ] **Registered in ADAPTERS**:
|
|
223
|
+
- [ ] **Registered in ADAPTERS**: the definition is declared under `src/adapters/definitions/` (or its own directory) with explicit `displayName`, `family`, `transport`, `trust` and `projection`, and listed in `ADAPTERS` (`src/adapters/registry.ts`). The peer lists are derived from it. The hand-kept cross-release copies in `src/hub/peer-catalog.ts` and `src/hub/start-mode.ts` and the pinned tests listed in `docs/agent-notes/adapters.md` change with it, and the PR names them as expected test changes.
|
|
136
224
|
- [ ] **Config Path Protection**: Agent configuration directories added to `AGENT_CONFIG_SEGMENTS` in `src/hub/permission-mode.ts`.
|
|
137
225
|
- [ ] **PII Safety Verified**: Cloud models have `trust.onPrem: false`; verify PII markers refuse routing to this peer.
|
|
138
226
|
- [ ] **Process Group Teardown**: Daemon-spawned processes use process groups and exit cleanly on stop.
|
|
@@ -20,3 +20,12 @@ The #451 predicate and identity-lookup moves exposed unbound names in a VM fixtu
|
|
|
20
20
|
that extracts daemon closures. Reconnecting imported helpers and deterministic
|
|
21
21
|
identity ports preserved the fixture's existing assertions; changing the asserted
|
|
22
22
|
behavior would have hidden the missing dependency.
|
|
23
|
+
|
|
24
|
+
Background moved out of the area notes for 0.12.45:
|
|
25
|
+
|
|
26
|
+
- Instance execution git budget (#225, daemon.md): `resolveInstanceExecutionCwd` runs synchronously on the daemon's event loop (an extra-instance start, `turn_revert`) with up to four git calls, measured at about 15 ms on a warm local repository. One shared 5 s allowance holds the loop at most 5 s, not 4 x 5 s. It stays synchronous because the CLI and recovery callers are; an async twin for the two daemon call sites is the upgrade if a typical resolution ever costs more than a few ms.
|
|
27
|
+
- Claude steer version gate (#493, #221, daemon.md): mid-turn consumption of a steered important message was proven only on Claude Code 2.1.302 (`docs/verification/2026-10-18-493-claude-busy-channel.md`); on an older client a dropped mid-turn notification stays `accepted` until a person resolves it. The hello does not carry the client version, and the oldest working version is unknown, so a gate would be a guess that could disable a working path.
|
|
28
|
+
- Planner reservation and done gates (tasks.md): in #378 a same-value planner write laundered a person's reservation; in #382 advise recorded overlapping dones, and an owner could neither relink a PR nor be stopped from carrying a head to a new PR.
|
|
29
|
+
- Process-tree stops (adapters.md): three review rounds of #113 found snapshot gaps while the tree ran (groups of its own, an unreadable first read, a child started during the grace period).
|
|
30
|
+
- Local worker (local-worker.md): a bare `private/` deny entry once denied all of `/private/var`; `existsSync` made a dangling link look like a new file, so a write landed at its target.
|
|
31
|
+
- Suite watchdog (tests.md): macOS CI hit `AHUB_CHECK_HANG_S` while tests still passed (#378, #593).
|
|
@@ -1,83 +1,97 @@
|
|
|
1
1
|
# Native adapters and spawned agents
|
|
2
2
|
|
|
3
|
+
Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` and `activity` events), peer turn state, approvals, and the agent processes the hub spawns and stops. Read before editing `src/adapters/`, `src/pi/`, `src/hub/peers.ts`, `src/hub/permission-mode.ts`, `src/hub/tool-refusal.ts`, `src/hub/child-process.ts`, `src/hub/lifecycle.ts` (shutdown deadline), `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/memory/capture.ts` (session end), `src/hub/native-hook-launch.ts`, `src/hub/peer-catalog.ts`, the launcher-managed MCP entries (`src/cli/agy-mcp.ts`, `src/cli/kimi-mcp.ts`, `src/cli/jcode-mcp.ts`) or the process-tree stop in `scripts/benchmarks/teardown.ts`, or before adding a native peer.
|
|
4
|
+
|
|
5
|
+
- For conceptual architecture, definition fields, generic ACP config and the new-adapter review checklist, see [Adding an AI Tool](../adding-an-ai-tool.md). For definition interfaces, see [registry-types.ts](../../src/adapters/registry-types.ts).
|
|
6
|
+
|
|
7
|
+
## Adapter definitions, the registry and external modules (#451)
|
|
8
|
+
|
|
9
|
+
- Keep per-kind native policy in `src/adapters/definitions/` and consume it through the registry. Private CLI implementations accept only fixed host-bound ports; keep identity, audit, lifecycle admission and effect ordering in the host. Except for the verified person-owned A4 startup-module contract, do not add config-selected callbacks or runtime imports from registry leaves into eagerly initialized hub/CLI modules; use type-only ports for existing host planners.
|
|
10
|
+
- Adapter registry (#451): kind-specific policy lives in that peer's `AdapterDefinition` under `src/adapters/definitions/` (runtime definitions `NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN` in `builtin-metadata.ts` plus `qwen.ts`; the daemon constructs every native driver through `start.create` and `DAEMON_PORTS`; `BUILTIN_DEFINITIONS_WITHOUT_QWEN` is the frozen A1 export, read by no runtime consumer). Outside `src/adapters/`, read a semantic field of `definitionOf(id)`: never a raw peer-id comparison, a resolved kind (`kindOf`, `definitionOf(...).kind`) or a constant alias compared with a native name, a vendor-keyed object, a peer-name array, or a closed-list import from `peer-catalog.ts`, `start-mode.ts` or `permission-mode.ts`; the guard resolves aliases and import/re-export bindings. `test/registry-guard.baseline.json` is `[]` and stays empty: a new site moves into a definition, never into the baseline. The only exceptions are the cross-release literals `KNOWN_PEERS`, `OPTIONAL_PEERS` and `START_MODE_PEERS`, which `test/registry-guard.test.ts` holds equal to the derived lists. `LaunchContext.nativeCommand` carries only validated config argv (or its redacted preview), never model input; a definition's `permission.grants` text is what `permissionGrant` shows, ahead of the built-in wording. Keep native mode-id maps in `definitions/*-native.ts`, free of registry factories (`test/adapter-import-order.test.ts`).
|
|
11
|
+
- Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, a `pi-rpc` daemon port, `routing`, `projection`, `reviewerVariant`, `reviewer`, `hooks.factsHook`, `cli.entry`, `cli.preview` or `cli.usage`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra with `start_mode: "headless"`, roles exactly `["reviewer"]`, and either a variant whose `reviewer.declaration` is `"automatic"` (Claude, agy) or `review_mode: "fresh"` (Codex, Kimi); without that opt-in a reviewer-only Codex or Kimi instance keeps its native adapter. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. A new field that depends on fixed-builtin assumptions or host-global native state joins that list (`test/adapter-capability-scope.test.ts`). `trust.onPrem` admits PII work (rule: `tasks.md`).
|
|
12
|
+
- Adding a native peer: declare its `AdapterDefinition` under `src/adapters/definitions/` (or its own directory, as agy) and list it in `ADAPTERS` (`src/adapters/registry.ts`). `KNOWN_PEERS`, `OPTIONAL_PEERS`, `SUPPORTED_PEERS`, `START_MODE_PEERS` (a kind with both a TUI and a headless form), `ROLE_ORDER`, `HELP_ORDER` and `PLANNER_PEERS` are derived from it by `deriveAdapterLists`: set `optional`, `projection` (`roleRank`, `startRank`, `helpRank`, `plannerEligible`; a configured or external definition cannot) and, for an `ahub <kind>` launcher, `cli.entry`, `cli.usage`, `cli.preview` and `cli.startWords`, never a list edit (`builtinCliEntries(definitions)` projects the entries). The literal cross-release copies `KNOWN_PEERS` and `OPTIONAL_PEERS` in `src/hub/peer-catalog.ts` and `START_MODE_PEERS` in `src/hub/start-mode.ts` stay hand-kept for older coordinators: `test/registry-guard.test.ts` fails when one differs from the derived list. Adding a peer is the one change that updates their probe counts and `VENDORS` in `test/helpers/registry-inventory.ts` (`REGISTRY_COMPATIBILITY_PROBES`), the probe literal in `test/adapter-registry-a2.test.ts`, the `ADAPTERS` order in `test/adapter-registry.test.ts` and the literal in `test/adapter-catalog-a2.test.ts`; say so in the PR as an expected test change. `isSupportedPeer` answers from `ADAPTERS`, and for a declared instance from its kind's runtime definition unless the instance resolves to an unavailable reviewer variant; `isKnownPeer` and `defaultPeerRoles` answer from `definitionOf`; `STARTABLE_PEERS` reuses `SUPPORTED_PEERS`.
|
|
13
|
+
- When an adapter lands, add its project config directories to `AGENT_CONFIG_SEGMENTS` (`src/hub/permission-mode.ts`); scoped automatic writes must ask there, while explicit approval and `never-ask` keep their existing behavior.
|
|
14
|
+
- Load external modules only at daemon startup from trusted untracked local declarations: verify the self-contained bundle and content pin before importing captured bytes. Keep CLI/reload paths metadata-only, modules restart-bound and never settings-editable, builtin capabilities restricted, and external-kind instances refused in A4 (follow-up #287). Export conformance registrations and fake assets without starting them on import.
|
|
15
|
+
- Bind scanned external runtime SDK imports to this daemon's own `/adapter` entry; never resolve them against project packages. Refuse `/conformance`, `bun:ffi`, `bun:test` and CommonJS loading at runtime. Isolate module refusals from hub startup and retain unknown module roster rows as inert when metadata is unavailable.
|
|
3
16
|
- Require the immutable captured project catalog at instance, roster, execution and permission resolver boundaries; choose a builtin catalog explicitly only for builtin-only callers; accept `adapters` only as wholly machine-local trusted data and require restart for changes. Refuse missing/mismatched pins, non-echoed load sessions, leader/attach argv and unknown owned-group evidence; require an explicit standalone `--no-leader` before argv termination for Grok (kind, executable or pinned native name); signal only the original verified group. Keep unmapped policy unmanaged, absent budgets exempt and read-only instances non-raisable through native acknowledgement plus host/MCP mutation gates. Claim native support only with its isolated T0 receipt.
|
|
4
17
|
- Consume quota/context through the resolved definition's declared readers; absence stays unknown. Use explicit `attach.externalHello` and `instances` policy for admission, never `identity.mcp`; keep runtime owner/root and reviewer isolation proof in the host. Never let configured `roles` include `conductor`. Keep native stderr, prompt-failure, permission-mode and transport error text out of configured-kind logs and turn-failure reasons. A configured CLI start (`src/cli/configured-acp-entry.ts`) accepts only `--mode headless`, `--as`, `--session-id` and one preview flag, and its preview never shows argv or session values.
|
|
5
18
|
- Validate coherence and conformance enrollment for reviewer variants too. Construct driven conformance peers through `start.create`; name dedicated coverage and concrete reasons for unsupported legs. Do not restore removed placeholder launch, lifecycle, init or permission metadata.
|
|
6
|
-
- For conceptual architecture, definition fields, generic ACP config and the new-adapter review checklist, see [Adding an AI Tool](../adding-an-ai-tool.md). For definition interfaces, see [registry-types.ts](../../src/adapters/registry-types.ts).
|
|
7
19
|
|
|
8
|
-
|
|
20
|
+
## Permissions, modes and approvals
|
|
9
21
|
|
|
10
|
-
- When an adapter lands, add its project config directories to `AGENT_CONFIG_SEGMENTS` (`src/hub/permission-mode.ts`); scoped automatic writes must ask there, while explicit approval and `never-ask` keep their existing behavior.
|
|
11
|
-
- Kimi TUI is a pull-only WsPeer under the one kimi id, with no hub busy/watchdog/permission/quota machinery. Only the person's launcher merges or removes its project MCP entry; a daemon headless start refuses a stale entry and never edits the file. Scrub mode/channel markers from the native child; managed AGENTS instructions and tool descriptions carry the pull rules, not MCP initialize instructions.
|
|
12
|
-
- Keep per-kind native policy in `src/adapters/definitions/` and consume it through the registry. Private CLI implementations accept only fixed host-bound ports; keep identity, audit, lifecycle admission and effect ordering in the host. Do not add config-selected callbacks or runtime imports from registry leaves into eagerly initialized hub/CLI modules; use type-only ports for existing host planners.
|
|
13
|
-
- Retire a cancelled Codex native turn until its exact completion or session teardown. Late start acceptance, answer frames and completion must not revive its audience or relabel uncertainty as `failed_safe`. If cancellation has no provable native id, keep the peer offline until verified reattachment.
|
|
14
|
-
- Codex: the adapter never sends its own `initialize`. It is a proxy; hub requests use negative ids and their responses must not reach the TUI.
|
|
15
|
-
- A durable Codex steer stays pending while its native turn runs. Its RPC may answer after `turn/completed`: bound unanswered replies from that turn's end, requeue a definitive late refusal, and hold a late acceptance as uncertain without attaching it to a later turn's audience (#296).
|
|
16
|
-
- Codex reports each thread it adopts, started or resumed (`onThread`, the `native_thread` event), before any turn on it starts: the upgrade planner (Codex's `session.zeroTurn` in `src/adapters/definitions/native-recovery.ts`, read through `nativeZeroTurn` in `src/cli/upgrade-runtime.ts`, #215) gives each codex `turn_start` to the thread adopted last before it and plans a thread it saw start with no turn (and no rollout) as a fresh start.
|
|
17
|
-
- Codex 0.154.0 `agentMessage` items carry `text` and `phase` (not `content[]`); only the last non-`commentary` message of a turn is shared.
|
|
18
22
|
- Approval titles are agent-written text shown to a person: the daemon escapes control characters, and write/edit/bash show what will be written or run.
|
|
19
23
|
- An ACP permission request may carry no `rawInput` (Kimi 2.0.1): the arguments were on the earlier `tool_call` update, which `acp.ts` remembers by `toolCallId`. Kimi 2.1.1 sends no `rawInput` before the answer at all: the argument JSON streams as `content` text on `tool_call_update`, and only complete JSON counts. A payload that still cannot be resolved, or is too long to show whole (marked `[cut, N chars]`), withholds its `allow_always` option - never render a bare tool name as if it described the call.
|
|
20
|
-
- Tag a Pi hub-tool refusal only at a proven pre-effect guard with `PreEffectToolRefusal`; return its exact error and settle its receipt. Never classify a generic/post-effect/storage failure or a legacy pending receipt from error text or current board state. After durable `markStale`, cancellation or output consumption, retain ordinary failure classification; a later binding/privacy rejection cannot prove that no effect occurred. Recheck task revision, owner/session generation and privacy across awaited bounded spec/check operations; never turn those tools into arbitrary host execution.
|
|
21
|
-
- Hide `hub_delivery_retry` from delivery-only ACP/local tool lists and omit its retry hint. Treat `AGENTHUB_DELIVERY_ONLY` only as an exposure hint; the daemon must refuse by the actual attached adapter, and native children must not inherit the marker. A Claude push that cannot reach its model reports `failed_safe`; if the daemon refuses that optional receipt state, try `needs_review` and retain uncertainty when neither receipt is confirmed.
|
|
22
|
-
- Report each Pi owner exit once, with code/signal (unknown for an unobserved TUI exit), turn/tool activity and only the last tool name. Auto-restart only a current, started, unexpectedly idle headless owner on its verified persisted session, once per 60 s; preserve shutdown/requested-stop/recovery/startup/active-tool fences and never fall back fresh.
|
|
23
|
-
- Pi's `always` grant (#209) is keyed by `executeTool`'s tool name, never parsed from the approval title, and lives only in that Pi start's closure. Never persist it, share it with the local worker, or let the dashboard give it.
|
|
24
24
|
- Auto-approval covers the hub's own tools by exact `mcp__agent-hub__<name>` title and only ever picks `allow_once`. Identity comes from the permission request title or, when an agent titles the request with the argument JSON (Qwen), from the announced `tool_call` title bound to the call id and resolved against the session's configured MCP servers, never from payload text, a prefix or an unrelated display title. Never widen it to a prefix or replace it with an agent-selected session mode. Configured ACP kinds (`nativePolicy` or `requireOwnedGroup` set) never auto-approve from a request or tool-call title, even an exact hub tool name; under a locked read-only mode, reject any request whose ACP `kind` is not read, search or think, and never offer `allow_always`. Set Kimi `yolo`/`auto` only for its verified native identity and an operator mode; config never-ask waits for a console y, and other ACP agents stay unmanaged on fresh/resumed starts without setting/resetting modes or failing over their reported mode; refuse runtime changes until their map exists; await the advertised mode change after session new/load and before a prompt, and fail startup on refusal. Use the shared `KIMI_MODE_IDS` mapping (`src/adapters/definitions/kimi-native.ts`, re-exported by `src/hub/permission-mode.ts`).
|
|
25
25
|
- Permission modes (#242) are a person's and live in the daemon's memory only: `permission` and `permission_default` are console-role control messages, `ahub permission` is `console` in `classifyPeerCommand`, and no hub tool or conductor tool sets one. The dashboard and `ahub settings` set one only through `settingsSet` (`src/hub/daemon.ts`, #269), which calls `changePermission` inside `permissionChange` as the console does: never add another path that writes a runtime choice to `permissionModes`; a reloaded project default (#439) is applied only by `applyPendingPermissionDefault`, and only while that peer is absent or offline. Never write a runtime choice to a restart snapshot.
|
|
26
26
|
- A headless ACP Kimi or Codex whose mode lives in the native agent joins the bus only through `reconcileNativeStart` (`src/hub/daemon.ts`): after `start()` the daemon's current mode is applied, then `bus.add` (it can drain at once), then the state `start()` reached is replayed. Every mode change runs inside `permissionChange`, one per peer at a time.
|
|
27
|
+
- Generic permission and generation dispatch in the daemon reads the optional `PeerAdapter` ports (`src/hub/peers.ts`, #667): `permissionModeState`, `setPermissionMode` (resolves only after native acknowledgement; changes approval frequency, never the sandbox), `sessionGeneration` and `checkpointUsesSessionGeneration`; never add a native-class predicate (`isAcpPeer`, `isCodexPeer`) for it. An absent `sessionGeneration` keeps the recovery-session/owner-token fallback; a present but undefined one is unknown and refuses. `BasePeer` installs no default generation, and only an explicit `checkpointUsesSessionGeneration = false` (Pi) skips the checkpoint generation fence. A `permissionModeState` of `unknown` or `unmanaged` refuses a change.
|
|
27
28
|
- Codex takes a mode as an `approvalPolicy` override on `turn/start`, and the override persists on the thread: pass the hub's and the TUI's `turn/start` alike through `applyPermissionPolicy`, and return to `ask` only by sending the captured native policy once. With none captured, refuse while the TUI is attached; never just stop sending the override. Keep the captured native policy and the restoration debt per thread (`nativeApprovalPolicies`, `overriddenApprovals`) across detach and `thread/start` or `thread/resume`; clear a debt only on a successful response to its restoring `turn/start` while the mode is `ask` (`settlePermissionRestore`), so a rejected turn keeps it. While a thread owes a restoration with a known baseline, in any mode, never replace that baseline: a `thread/resume` response and the TUI's own `turn/start` can both echo the sticky override, and the restoring turn sends the kept baseline whatever the TUI supplied. That protection is one turn: after the restoration succeeded, a policy the TUI sends is taken as the person's choice, captured and passed through, because the proxy cannot tell a repeated echo from one. A detached proxy's `clearPermissionMode` is never refused; if the native policy is unknown, return a CLI-visible note that the thread keeps its last policy. Attached refusal directs the person to close the TUI, then run `ahub permission codex ask`.
|
|
28
29
|
- An unanswered `session/set_mode` leaves the native mode unknown, not unchanged: `AcpPeer` invalidates the turn, goes offline, stops its process group and refuses further changes until restarted. Never report the old or the requested mode after a timeout.
|
|
29
30
|
- A mode grant in `piPermit` or the local worker's `permit` (`src/hub/daemon.ts`) answers as the prompt path does: allowed only while that owner is still the attached one and the call was not aborted. `ask-when-needed` covers `PI_EDIT_TOOLS` on a `grantablePath` only (path rule: `local-worker.md`).
|
|
30
|
-
- Every agent the hub spawns (the Codex app-server, ACP agents such as Kimi, Pi) runs in its own process group and is stopped as one (`stopOwnedProcess(proc, { group: true })`, `trackGroup` after spawn): `codex` is a node launcher, and mid-turn its native app-server does not exit on SIGTERM within the grace period, so a SIGKILL to the launcher alone left the app-server running under init and holding the daemon alive (#113, #115). A new builtin adapter that spawns an agent does the same. A configured ACP kind stops only through `AcpOwnedGroup` (`src/adapters/acp-owned-group.ts`): capture the direct child and its original group after spawn, verify before admission and again before stop, SIGSTOP, re-verify, then SIGKILL only that group; never discover or signal descendants outside it, and with unproven ownership send no signal, close stdin and keep the owner until it is confirmed gone.
|
|
31
|
-
- To stop a process tree, freeze it (SIGSTOP) before reading what it contains, then SIGKILL, and call it done only when the table shows none of it (with no table at all, the stop can only ask the group itself, which cannot see groups below it). A process a read after the freeze shows for the first time is frozen and read again before anything is killed. A snapshot taken while the tree runs misses what it starts next: three review rounds of #113 found such a gap (groups of its own, an unreadable first read, a child started during the grace period).
|
|
32
|
-
- Work that must survive `ahub kill` (claude-mem `session-end`) is awaited in `stop()`; fire-and-forget dies with the process.
|
|
33
|
-
- A peer must set `busy` synchronously inside `deliver()`, otherwise the bus drains the next envelope into a running turn (Kimi answers `turn.agent_busy`). Outside a turn the adapter accepts an occupancy diagnostic (a `context-used` `usage_update` naming its current session) and nothing else: no token total, no chunk, nothing unnamed or for an offline peer (#285).
|
|
34
|
-
- A watchdog-cancelled turn still reports later. Anything a turn does on completion must check it is still the current turn (`turn` generation in `acp.ts`, `activeTurns` in `codex-appserver.ts`). A rejected prompt other than a recognised quota refusal, or an abnormal prompt end, reports through the adapter's `onTurnFailure` (same shape in `acp.ts` and `pi.ts`), even when ACP streamed partial text; a stale cancelled turn never reports it; a rejected watchdog-cancelled ACP prompt during its journal-less wait is discarded with one log line, not requeued as on the earlier adapter.
|
|
35
|
-
- Native owner teardown must handle a lost shutdown acknowledgement using verified process identity. Pending launches must be revocable, and active tools/streaming output must count as watchdog activity. A reloaded `watchdog_ms` reaches a peer only through `configureWatchdog` (`src/hub/peers.ts`, #439) and applies from its next busy turn, never to the running one.
|
|
36
|
-
- ACP tool ceiling (#542): a tool call announced (`tool_call`) and not yet `completed` or `failed` in the current turn keeps a silent busy turn alive past the watchdog, up to `AcpOptions.toolCeilingMs` (never below the watchdog) counted from the last protocol message. Re-arm with `touch(<remaining ceiling, at most the watchdog>)`, never a full extra watchdog, and clear the open calls at each `deliver()`. Past the ceiling, cancel as an ordinary watchdog; its log line names the open tools only on a turn that is not private (latch `isPrivateTurn`, fail closed when it throws), each title as one sanitized, quoted, cut line.
|
|
37
|
-
- Keep Pi owner identity on the shared `processSignature`/`processLiveness` contract exported by the exact-pinned package's Node-safe `@staix/agent-mem/platform` entry, re-exported unchanged by `src/pi/process-signature.ts` for the startup claim, extension claim, TUI owner monitor, `recoveryReady` and verified teardown. Memory's `sync.lock` uses the same implementation. Preserve the Node-safe process-entry regression in `test/memory-parity.test.ts`, the existing Pi process behavior tests, the LC_ALL=C/TZ=UTC `ps` read and strict pid + start + command identity; never accept PID-only or a fuzzy command match.
|
|
38
|
-
- Return the shared `toolResultFailed` verdict as `/tool`'s `failed`; map only `failed === true` to Pi's `isError`. Preserve text and the older bridge's absent-flag behavior. The one exception is a person's `!command` whose process exit was observed: its `failed` is `exitCode !== 0` and the reply carries `exitCode`, from `onProcessExit` through `packPiShellResult`, never parsed from the output (#253).
|
|
39
|
-
- Pi's bridge executes `/tool` only for the verified owner (`verifiedOwner()`: the claimed pid with its `processSignature`; a headless owner is pinned from the spawned process once `get_state` proved its session) with the current `sessionId` and turn `generation`, and admits `/budget` only for that owner and generation, checked again after the awaited admission. Refuse anything else before execution. `tool_abort` aborts that pending call, or fences its id when it arrives before the call (64 ids a turn, then every further call of that turn). A person's `!command` (`purpose: "idle_user_bash"`) is taken only in TUI mode from an idle owner on its one-use reservation, and it alone skips `piPermit`: never widen that to a model's bash call (#253).
|
|
40
31
|
- Pi and local approvals (#253): `permit` answers a `ToolApproval` (contract: `local-worker.md`) and `requestPermission` (`src/hub/daemon.ts`) tells the adapter's `ApprovalObserver` what settled it. An expiry counts toward the stop only as `source: "expired"` with `eligibleExpiry` not false (no person answered a request of that peer while it was pending); `person` resets the count, `automatic` (a mode grant, Pi's `always`) never does, and a new turn starts it over. The second one ends the turn, never the peer: local throws `ApprovalWaitStop`, Pi sends `abort_budget` with `cause: "approval"`, and both settle the delivery `completed` with the reason; a Pi whose extension did not announce `approvalTurnAbort` goes offline with the delivery held. `requestPermission` deletes a request from `permissions` before it runs an observer, resolves or reports, and an aborted signal closes it as `cancelled`: a late answer must find nothing to grant, and a failed event, notice or console push never strands the caller.
|
|
41
|
-
- `
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
- Emit Pi's ceiling event at the pre-effect rejection boundary (`src/pi/ceiling.ts`), before `agent_end`. Accept only a valid current-session/current-generation signal during a running turn; retain the first signal and attach it to that turn's failure. Never classify a ceiling from free text.
|
|
32
|
+
- Pi's `always` grant (#209) is keyed by `executeTool`'s tool name, never parsed from the approval title, and lives only in that Pi start's closure. Never persist it, share it with the local worker, or let the dashboard give it.
|
|
33
|
+
- Refuse ACP permissions while offline or waiting for watchdog cancellation before consulting automatic or human approvers. Abort pending permission signals on watchdog, offline transition and stop; the daemon withdraws their registered console approvals before any late person answer. Restart creates new requests without reviving withdrawn ones (#311).
|
|
34
|
+
- Apply QWEN_MODE_IDS only to a production qwen025 adapter with verified qwen-code 0.25.0 native identity and advertised ids; legacy benchmark ACP stays unmanaged. Await the native acknowledgement before bus admission, preserve existing approval fences, and never map auto or plan. No Qwen quota classification exists until a pinned native refusal is observed.
|
|
45
35
|
|
|
36
|
+
## Turns, watchdogs and hub tools
|
|
37
|
+
|
|
38
|
+
- A peer must set `busy` synchronously inside `deliver()`, otherwise the bus drains the next envelope into a running turn (Kimi answers `turn.agent_busy`). Outside a turn the adapter accepts an occupancy diagnostic (a `context-used` `usage_update` naming its current session) and nothing else: no token total, no chunk, nothing unnamed or for an offline peer (#285).
|
|
39
|
+
- A watchdog-cancelled turn still reports later. Anything a turn does on completion must check it is still the current turn (`turn` generation in `acp.ts`, `activeTurns` in `codex-appserver.ts`). A rejected prompt other than a recognised quota refusal, or an abnormal prompt end, reports through the adapter's `onTurnFailure` (same shape in `acp.ts` and `pi.ts`), even when ACP streamed partial text; a stale cancelled turn never reports it; a rejected watchdog-cancelled ACP prompt during its journal-less wait is discarded with one log line, not requeued as on the earlier adapter.
|
|
46
40
|
- After an ACP watchdog cancels a prompt on a bus without a journal, keep the peer busy with its inactivity watchdog cleared until that prompt settles; suppress its turn-scoped updates and permissions while still accepting session-bound occupancy diagnostics. Bound the wait by the existing 30 s ACP handshake deadline, go offline, never idle, with queued work retained if it expires, and revoke the wait on stop or transport loss; a durable delivery keeps its existing offline/needs_review path.
|
|
41
|
+
- ACP tool ceiling (#542): a tool call announced (`tool_call`) and not yet `completed` or `failed` in the current turn keeps a silent busy turn alive past the watchdog, up to `AcpOptions.toolCeilingMs` (never below the watchdog) counted from the last protocol message. Re-arm with `touch(<remaining ceiling, at most the watchdog>)`, never a full extra watchdog, and clear the open calls at each `deliver()`. Past the ceiling, cancel as an ordinary watchdog; its log line names the open tools only on a turn that is not private (latch `isPrivateTurn`, fail closed when it throws), each title as one sanitized, quoted, cut line.
|
|
42
|
+
- Retire a cancelled Codex native turn until its exact completion or session teardown. Late start acceptance, answer frames and completion must not revive its audience or relabel uncertainty as `failed_safe`. If cancellation has no provable native id, keep the peer offline until verified reattachment.
|
|
43
|
+
- Codex: the adapter never sends its own `initialize`. It is a proxy; hub requests use negative ids and their responses must not reach the TUI.
|
|
44
|
+
- A durable Codex steer stays pending while its native turn runs. Its RPC may answer after `turn/completed`: bound unanswered replies from that turn's end, requeue a definitive late refusal, and hold a late acceptance as uncertain without attaching it to a later turn's audience (#296).
|
|
45
|
+
- Codex reports each thread it adopts, started or resumed (`onThread`, the `native_thread` event), before any turn on it starts: the upgrade planner (Codex's `session.zeroTurn` in `src/adapters/definitions/native-recovery.ts`, read through `nativeZeroTurn` in `src/cli/upgrade-runtime.ts`, #215) gives each codex `turn_start` to the thread adopted last before it and plans a thread it saw start with no turn (and no rollout) as a fresh start.
|
|
46
|
+
- Codex 0.154.0 `agentMessage` items carry `text` and `phase` (not `content[]`); only the last non-`commentary` message of a turn is shared.
|
|
47
|
+
- Hide `hub_delivery_retry` from delivery-only ACP/local tool lists and omit its retry hint. Treat `AGENTHUB_DELIVERY_ONLY` only as an exposure hint; the daemon must refuse by the actual attached adapter, and native children must not inherit the marker. A Claude push that cannot reach its model reports `failed_safe`; if the daemon refuses that optional receipt state, try `needs_review` and retain uncertainty when neither receipt is confirmed.
|
|
48
|
+
- ACP `onActivity` is a closed, transient person-only projection. Never publish it through the bus, log/event writers, UI, tools or memory. Emit only for an active turn; latch private envelopes and associated PII work, including unreadable privacy state, and discard pending chunks on turn teardown. Native terminal references do not authorize filesystem/terminal RPC reads.
|
|
47
49
|
- ACP quota refusals use the pinned agent contract in `src/adapters/acp-quota.ts`, fenced by native identity and current turn generation. Report hard quota and require the hub's explicit pause confirmation before settling a pre-acceptance refusal as `failed_safe`; retain `needs_review` when work already started, and keep unknown refusals on the ordinary failure path. Sanitize agent refusal text before hub-written output.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
## Spawned processes and teardown
|
|
50
52
|
|
|
51
|
-
-
|
|
53
|
+
- Every agent the hub spawns (the Codex app-server, ACP agents such as Kimi, Pi) runs in its own process group and is stopped as one (`stopOwnedProcess(proc, { group: true })`, `trackGroup` after spawn): `codex` is a node launcher, and mid-turn its native app-server does not exit on SIGTERM within the grace period, so a SIGKILL to the launcher alone left the app-server running under init and holding the daemon alive (#113, #115). A new builtin adapter that spawns an agent does the same. A configured ACP kind stops only through `AcpOwnedGroup` (`src/adapters/acp-owned-group.ts`): capture the direct child and its original group after spawn, verify before admission and again before stop, SIGSTOP, re-verify, then SIGKILL only that group; never discover or signal descendants outside it, and with unproven ownership send no signal, close stdin and keep the owner until it is confirmed gone.
|
|
54
|
+
- To stop a process tree, freeze it (SIGSTOP) before reading what it contains, then SIGKILL, and call it done only when the table shows none of it (with no table at all, the stop can only ask the group itself, which cannot see groups below it). A process a read after the freeze shows for the first time is frozen and read again before anything is killed.
|
|
55
|
+
- Work that must survive `ahub kill` (claude-mem `session-end`) is awaited in `stop()`; fire-and-forget dies with the process.
|
|
56
|
+
- Native owner teardown must handle a lost shutdown acknowledgement using verified process identity. Pending launches must be revocable, and active tools/streaming output must count as watchdog activity. A reloaded `watchdog_ms` reaches a peer only through `configureWatchdog` (`src/hub/peers.ts`, #439) and applies from its next busy turn, never to the running one.
|
|
57
|
+
- `stop(reason?)` (`src/hub/peers.ts`): a reason marks a person's requested stop (`peer_stop`, rule: `daemon.md`). Fence before the first await (the adapter's stopping flag, or `offline`) so `deliver()` and buffered native callbacks do nothing more, hold the in-flight delivery as `needs_review` with that reason, and end `offline`. For ACP without a journal, return ordinary in-flight envelopes once through `onFailed` before a requested stop invalidates the turn; never return the watchdog-cancelled original prompt. A requested stop never completes, escalates or reports the turn it cut (`AcpPeer` bumps `turn`; `PiPeer.fail` skips the failure message and `onTurnFailure` while `requestedStopReason` is set). `PiPeer.stop()` waits for aborted tools only up to `stopGraceMs`: a tool past it never blocks the teardown, and the stop throws `PiToolSettlementFailure` once the owner is down.
|
|
58
|
+
- A terminal the hub opens for a person (`src/hub/terminal-open.ts`, #269) gets `personEnv`, which strips every agent marker; never `peerChildEnv`, which sets one.
|
|
59
|
+
- Spawn a native peer with `peerChildEnv(peer, source)` so its own `AGENTHUB_PEER_ID` replaces inherited agent markers. Sandbox tool environments carry their executing peer too. Preserve the native shell environment policy; Codex's `CODEX_THREAD_ID` supplies the fallback after filtering. Before changing marker detection or launcher environments, read `docs/verification/2026-10-09-agent-shell-t0.md` for pinned source and live-probe limits.
|
|
60
|
+
- Spawn with `peerChildEnv(<instance id>)`, not the kind name. An ACP `kind` comes only from the host declaration, never from the id suffix or agent text.
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
## Pi
|
|
54
63
|
|
|
64
|
+
- Tag a Pi hub-tool refusal only at a proven pre-effect guard with `PreEffectToolRefusal`; return its exact error and settle its receipt. Never classify a generic/post-effect/storage failure or a legacy pending receipt from error text or current board state. After durable `markStale`, cancellation or output consumption, retain ordinary failure classification; a later binding/privacy rejection cannot prove that no effect occurred. Recheck task revision, owner/session generation and privacy across awaited bounded spec/check operations; never turn those tools into arbitrary host execution.
|
|
65
|
+
- Report each Pi owner exit once, with code/signal (unknown for an unobserved TUI exit), turn/tool activity and only the last tool name. Auto-restart only a current, started, unexpectedly idle headless owner on its verified persisted session, once per 60 s; preserve shutdown/requested-stop/recovery/startup/active-tool fences and never fall back fresh.
|
|
66
|
+
- Keep Pi owner identity on the shared `processSignature`/`processLiveness` contract exported by the exact-pinned package's Node-safe `@staix/agent-mem/platform` entry, re-exported unchanged by `src/pi/process-signature.ts` for the startup claim, extension claim, TUI owner monitor, `recoveryReady` and verified teardown. Memory's `sync.lock` uses the same implementation. Preserve the Node-safe process-entry regression in `test/memory-parity.test.ts`, the existing Pi process behavior tests, the LC_ALL=C/TZ=UTC `ps` read and strict pid + start + command identity; never accept PID-only or a fuzzy command match.
|
|
67
|
+
- Return the shared `toolResultFailed` verdict as `/tool`'s `failed`; map only `failed === true` to Pi's `isError`. Preserve text and the older bridge's absent-flag behavior. The one exception is a person's `!command` whose process exit was observed: its `failed` is `exitCode !== 0` and the reply carries `exitCode`, from `onProcessExit` through `packPiShellResult`, never parsed from the output (#253).
|
|
68
|
+
- Pi's bridge executes `/tool` only for the verified owner (`verifiedOwner()`: the claimed pid with its `processSignature`; a headless owner is pinned from the spawned process once `get_state` proved its session) with the current `sessionId` and turn `generation`, and admits `/budget` only for that owner and generation, checked again after the awaited admission. Refuse anything else before execution. `tool_abort` aborts that pending call, or fences its id when it arrives before the call (64 ids a turn, then every further call of that turn). A person's `!command` (`purpose: "idle_user_bash"`) is taken only in TUI mode from an idle owner on its one-use reservation, and it alone skips `piPermit`: never widen that to a model's bash call (#253).
|
|
69
|
+
- Emit Pi's ceiling event at the pre-effect rejection boundary (`src/pi/ceiling.ts`), before `agent_end`. Accept only a valid current-session/current-generation signal during a running turn; retain the first signal and attach it to that turn's failure. Never classify a ceiling from free text.
|
|
55
70
|
- Pi TUI hub prompts (#379; an idle snapshot ACK arriving before a personal start inserted a hub prompt into the person's turn): attribute a start, end or settle to a hub turn only by the command's `promptId`, which lasts until the logical `agent_settled`; treat any other start while a hub prompt is pending or running as personal activity that detaches Pi. Settle `failed_safe` only on a `prompt_not_started` refusal bound to the verified owner, session, command id and generation; everything else stays `needs_review`. Refuse roster return without `fencedPrompt`.
|
|
56
|
-
|
|
57
|
-
- Adapter registry (#451): kind-specific policy lives in that peer's `AdapterDefinition` under `src/adapters/definitions/` (runtime definitions `NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN` in `builtin-metadata.ts` plus `qwen.ts`; the daemon constructs every native driver through `start.create` and `DAEMON_PORTS`; `BUILTIN_DEFINITIONS_WITHOUT_QWEN` is the frozen A1 export, read by no runtime consumer). Outside `src/adapters/`, read a semantic field of `definitionOf(id)`: never a raw peer-id comparison, a resolved kind (`kindOf`, `definitionOf(...).kind`) or a constant alias compared with a native name, a vendor-keyed object, a peer-name array, or a closed-list import from `peer-catalog.ts`, `start-mode.ts` or `permission-mode.ts`; the guard resolves aliases and import/re-export bindings. `test/registry-guard.baseline.json` is `[]` and stays empty: a new site moves into a definition, never into the baseline. The only exceptions are the cross-release literals `KNOWN_PEERS` (5), `OPTIONAL_PEERS` (3) and `START_MODE_PEERS` (4), pinned separately in `test/registry-guard.test.ts`. `LaunchContext.nativeCommand` carries only validated config argv (or its redacted preview), never model input; a definition's `permission.grants` text is what `permissionGrant` shows, ahead of the built-in wording. Keep native mode-id maps in `definitions/*-native.ts`, free of registry factories (`test/adapter-import-order.test.ts`).
|
|
58
|
-
- Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, a `pi-rpc` daemon port, `routing`, `reviewerVariant`, `reviewer` or `hooks.factsHook`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra with `start_mode: "headless"`, roles exactly `["reviewer"]`, and either a variant whose `reviewer.declaration` is `"automatic"` (Claude, agy) or `review_mode: "fresh"` (Codex, Kimi); without that opt-in a reviewer-only Codex or Kimi instance keeps its native adapter. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. A new field that depends on fixed-builtin assumptions or host-global native state joins that list (`test/adapter-capability-scope.test.ts`). `trust.onPrem` admits PII work (rule: `tasks.md`).
|
|
59
71
|
- `validatePiSession` (`src/pi/session.ts`) throws `PiSessionUnresumable` only on positive evidence (ENOENT, a file outside the managed session directory, an unparsable or mismatched header) and `PiSessionBookmarkMismatch` only when a valid header names another session id; every other read failure is rethrown unchanged, because the daemon retires a Pi continuity bookmark only on those two classes (#463). `PiPeer` rethrows a mismatch with the neutral header-id message and never claims a bookmark exists (`test/pi-session-validation.test.ts`).
|
|
72
|
+
- Pi terminal ownership reads only the existing monitor's current pid/signature/session/file/token observation, expiring after 1 s; unknown verification withdraws proof. Keep fresh shared process-signature checks at tool/effect admission and teardown, and never revive terminal proof across detach, offline, stop or a changed owner/session (#576).
|
|
60
73
|
|
|
61
|
-
|
|
74
|
+
## TUI launchers, native facts and Antigravity
|
|
62
75
|
|
|
76
|
+
- Kimi TUI is a pull-only WsPeer under the one kimi id, with no hub busy/watchdog/permission/quota machinery. Only the person's launcher merges or removes its project MCP entry; a daemon headless start refuses a stale entry and never edits the file. Scrub mode/channel markers from the native child; managed AGENTS instructions and tool descriptions carry the pull rules, not MCP initialize instructions.
|
|
63
77
|
- TUI launchers (Kimi, Jcode, agy) write the plugin bundle the hub reports in `status` into their MCP entry, and the CLI's own copy only when the hub reports none (#502). Only the Jcode start path checks the entry against the daemon's own `SERVER_JS` (`jcodeMcpReady`); Kimi TUI admission does not compare it.
|
|
64
78
|
- Jcode (#529): accept the native version only through `jcodeVersionSupported` (from `JCODE_MIN_VERSION` up to, not including, `JCODE_BREAKING_VERSION`), in both the version probe and ACP `expectedAgent`; never pin the minor, and lower the breaking version when a release breaks the contract. Every owned jcode process (probe, serve, ACP and TUI) gets `JCODE_OWNED_ENV`, and the private socket name ends in `.sock`.
|
|
65
79
|
- Jcode TUI (#464): use only the current owned launch's top-level `--socket`/`--resume` tuple after native saved-session proof. Prove the save only through `savedSession` (no-follow, single-link regular file, size-bounded, unchanged across the read, its `id` and `messages` matching) and refuse a requested model, provider profile or session that differs from the running launch with `JcodeOptionMismatch`, which names the flag, never its value (#559); both the launcher and `--print-command` report it through `refuseNamedOption` (#574). Hand the tuple only to a person's launcher through `native_tui_launch` (admission: `daemon.md`); previews (`--dry-run`, or `--print-command` from anyone but a person) start nothing and never resolve it. Never synthesize a save or prompt to induce one. Ctrl-C during the save wait and frontend close retain ACP; preserve session/load identity refusal and headless owned recovery metadata, and never record the TUI as a managed terminal (`recordTerminalLaunch`): Jcode has no terminal recovery adapter (#564).
|
|
66
80
|
- Jcode steer (#572): reach a busy owned jcode only through `nativeSteer` on its private server socket, never by cancelling its turn: `ping` must list `soft_interrupt_ids`, `subscribe` uses `target_session_id` (the ACP session id) and `continue_on_disconnect`, `state` must report `is_processing` (a soft interrupt sent while idle starts a turn by itself), then `soft_interrupt {content, urgent: false, client_id}`. `canSend` rechecks the captured turn, busy state, owned server process, socket identity and session immediately before the write. Every refusal before the send is `failed_safe` with nothing sent; a sent message that is not acknowledged, or whose turn cannot be confirmed, is `needs_review`, never `failed_safe` or `completed`. Read `currentTurn()` before sending and adopt only through `AcpPeer.adoptIntoTurn(turn, ...)`, which refuses a different or ended turn; adopted senders join the turn answer's audience (`replyParent` and `replyAudience` over the turn envelopes, so the hop cap holds), and each steer receipt is `accepted` at once and settles with the turn (`completed` on a normal end, `needs_review` on a cancel, failure, stop or watchdog). Headless Kimi and Qwen have no steer: status reports their waiting important messages only through `importantWaitingStatus` (`src/hub/daemon.ts`) past `budget.stale_min`. A turn that ends and restarts between jcode's `state` reply and the write still receives the message, held `needs_review` and adopted into nobody: keep that ceiling until upstream offers an atomic per-turn steer, and never add a cancel-to-deliver path.
|
|
67
81
|
- Jcode MCP set (#572): `jcode acp` and `jcode serve` start every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, and no flag or variable selects them (`--mcp-tools`, `JCODE_MCP_TOOLS`, `JCODE_DISABLED_TOOLS` and `JCODE_DISABLE_CLAUDE_MCP` do not). Do not paper over it: `ahub doctor` lists the extra names through `jcodeExtraMcpServers` (`src/adapters/definitions/native-cli-jcode-mcp.ts`), names only, those four paths only, no home search, no link followed. Below the project root or home, `.jcode` and `.claude` must be real directories and the file a regular file (per-component `lstat`, an `O_NOFOLLOW` open and an `fstat` identity recheck); a system alias at or above the configured root stays allowed. Revisit if upstream adds a server selector.
|
|
68
|
-
-
|
|
69
|
-
-
|
|
82
|
+
- Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` outside the 1.4.x line (1.4.2 is T0-verified; other 1.4.x patches launch with a stderr warning, #589), and a hub without the `native_hook_launch` feature.
|
|
83
|
+
- 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.
|
|
84
|
+
- 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. `decodeAgyHook` classifies as `Read` only the observed native read (`view_file` `AbsolutePath`, or 1.4.3 `read_file` `Path`); a `StartLine`/`EndLine` range becomes `offset`/`limit`, so a partial read never counts as a whole-file view. Unobserved edits get no scoped grant or effect attribution. The daemon records Pre/PostToolUse through `Facts.preTool`/`postTool` and offers nothing on them (#555).
|
|
85
|
+
- `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.
|
|
86
|
+
|
|
87
|
+
## Reviewers
|
|
88
|
+
|
|
70
89
|
- Keep the default reviewer kind order Claude, Codex, Kimi (selection rules: `tasks.md`).
|
|
71
90
|
- Keep the retained agy reviewer slot unavailable with exact reason `agy is an implementer peer (#485)`, including explicit reviewer-pool requests. Do not add agy reviewer launch/admission or personal configuration writes to #234; implementer work belongs to #485.
|
|
91
|
+
- For fresh Kimi reviewers and task-bound MCP (#533), retain the common per-child task/head/review-revision/generation verdict fences, privacy screening and cleanup-before-settlement. Expose only a host in-process two-tool MCP capability for `hub_review` and `hub_task_show`; never give the native child the full hub control token. Supply source evidence only through the screened review brief/read result and host facts; exclude native Read/file/shell tools. Admit covered Kimi 2.2.0 only when current runtime preparation passes; otherwise keep it unavailable with the refusal reason. Keep actual interactive-TUI/usage ceilings separate from completed live/fake receipts; do not reuse historical Seatbelt/dummy-provider probes as revised-contract passes.
|
|
92
|
+
- Start fresh Kimi 2.2.0 without Seatbelt, with private `KIMI_CODE_HOME`, managed-provider-only configuration, clean temporary cwd, explicit empty skills directory and exactly `mcp__agent-hub__hub_review` plus `mcp__agent-hub__hub_task_show` in its agent tools. Use directory symlinks for the person's native `credentials/` and `oauth/`, and link `device_id` to the native identity; do not copy token files, select static-API-key mode or set `KIMI_DISABLE_OAUTH_LOCK`. Let native OAuth share refresh/atomic rename/`proper-lockfile` coordination and the existing Kimi account budget. Revoke verdict authority before shutdown; allow bounded TERM grace while the OAuth lock is held, then use verified owned-group teardown. Report a forced-kill/re-login requirement without attempting host auth repair.
|
|
72
93
|
- Bind every running print review to its immutable envelope head and review revision. Recheck `Tasks.reviewBindingCurrent` for task-MCP operations, legacy print reads and the verdict authority after awaits; an old child never gains a replacement request. A committed verdict acknowledgement does not authorize subsequent reads or verdicts.
|
|
73
94
|
- `ReviewerPrintPeer` (`src/adapters/reviewer-print.ts`, #223, #519; `ClaudePrintPeer` is its Claude wrapper) runs one fresh child per review and clears `reviewing` at the leader's exit and first in `kill()`. A stop or watchdog settles `needs_review` at once, before the kill; `completed`, `idle` and `offline` come only after the whole process group is confirmed gone, and a failed cleanup keeps the child handle and stays `busy`. The daemon's `reviewAuthority` compares `reviewToken`: never widen it.
|
|
74
95
|
- A reviewer backend is a fixed builtin `ReviewerBackend` in a definition's `reviewer`, never config. Its `prepare` must return a complete isolation receipt (matching backend id, binary, version, profile, evidence and `readOnly` true, plus `strictMcp` and `ephemeral` true, or for a `nativePolicy` backend `enforcement: "native-policy"`, `freshSession` and `hubToolsOnly` true) before every child. A `taskMcp` backend refuses without the host's `taskMcp` port and gets only that task's MCP config, never the hub-wide one. `admit(env)` and `launch.current()` are rechecked right before spawn, and a `watchCurrent` launch is polled while the review runs: a change aborts it as `review policy changed`; a missing or failed proof refuses, never a weaker launch or a continuing native session. The daemon puts the reviewer on the bus only after `start()` proved isolation; a failed start or a `start_failed` report marks it in `reviewerUnavailable`, which routing reads as `failing`. An unavailable reviewer variant has no reviewer backend or launch code; `startPeerBody` refuses it before preflight, independently of any native TUI launcher.
|
|
75
96
|
- Report reviewer failures (`onUnavailable`: `start_failed`, `quota`, `no_verdict`) only after the child's group is confirmed gone and only while its generation is current: a person's stop owns its hold and never reroutes. `quota` comes only from the backend's structured `failure(event)`, never from vendor text. An NDJSON frame over 256 KiB or more than 4 MiB of output aborts the review as `needs_review`.
|
|
76
97
|
- Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only the pinned `codex-cli` version; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`.
|
|
77
|
-
|
|
78
|
-
- Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` outside the 1.4.x line (1.4.2 is T0-verified; other 1.4.x patches launch with a stderr warning, #589), and a hub without the `native_hook_launch` feature.
|
|
79
|
-
- 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.
|
|
80
|
-
- 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. `decodeAgyHook` classifies as `Read` only the observed native read (`view_file` `AbsolutePath`, or 1.4.3 `read_file` `Path`); a `StartLine`/`EndLine` range becomes `offset`/`limit`, so a partial read never counts as a whole-file view. Unobserved edits get no scoped grant or effect attribution. The daemon records Pre/PostToolUse through `Facts.preTool`/`postTool` and offers nothing on them (#555).
|
|
81
|
-
- `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.
|
|
82
|
-
|
|
83
|
-
- Pi terminal ownership reads only the existing monitor's current pid/signature/session/file/token observation, expiring after 1 s; unknown verification withdraws proof. Keep fresh shared process-signature checks at tool/effect admission and teardown, and never revive terminal proof across detach, offline, stop or a changed owner/session (#576).
|
|
@@ -17,7 +17,7 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
|
|
|
17
17
|
- Kimi's ACP `{used, size}` occupancy is a context reading (`acpContext`, source `acp_usage_update`) bound to the adapter's own session id: never a `tokens` event or a budget window, and an invalid shape reports unknown, not the previous value. A gate crossing sends a checkpoint request only to definitions with `budget.checkpoint` (Claude and Codex); an ACP peer gets the event and the notice (#285).
|
|
18
18
|
- A quota resume notice replaces the peer's unread resume notices: carry their moves into the new one (`refs.budgetResumeMoves`, or the whole text of a notice that has none), publish it, and only then withdraw the ones it replaces, so a failed publish keeps them (#306).
|
|
19
19
|
- Quota handoff context reads `QUOTA_HANDOFF_PLATFORMS` (`src/adapters/registry.ts`, each definition's `hooks.recallPlatform`: claude, codex, kimi, qwen, jcode). It is independent of `hooks.recallInjected`: a handoff platform never joins the injected-platform exclusion in recall (`test/adapter-handoff-platforms.test.ts`).
|
|
20
|
-
- `Budget.report` resolves the reporter and its account through `hooks.definitionOf` (the daemon's startup-bound project catalog; builtin-only when absent) and skips `user`, `hub` and a registered kind, configured kinds included, with no `budget` block or with `budget.quotaExempt` (only local); an
|
|
20
|
+
- `Budget.report` resolves the reporter and its account through `hooks.definitionOf` (the daemon's startup-bound project catalog; builtin-only when absent) and skips `user`, `hub` and a registered kind, configured and external module kinds included, with no `budget` block or with `budget.quotaExempt` (only local); an unregistered id keeps quota accounting. `budget.instrumented` (execution budget) never exempts quota.
|
|
21
21
|
|
|
22
22
|
- Key quota readings, pause records, overrides, checkpoints and handoffs by the canonical account (`hooks.account`, idempotent); telemetry keeps the reporter id and native context occupancy stays per instance. `instanceBudgetHooks` fans pause, resume, checkpoint and handoff out to every member, and `canHandOff` excludes the paused account's siblings. Startup normalization merges old alias pause rows without dropping a row or marking an unfinished handoff done. A manual pause stays per member. Reviewer variants keep their base kind's `budget.account`.
|
|
23
23
|
- Release-cut quota admission (`releaseQuotaSnapshot` in `src/hub/daemon.ts`, #481 S11): an account has a quota source only when one of its members' definitions has `budget.reader`, or `budget.resetSoftTokensOnResume` with `kimi_tokens_5h > 0`. Without a source a registered account is reported `no quota source` and never defers; with one, an unknown, stale or below-gate reading defers. Manual and budget-record pauses defer for every attached peer.
|