@staix/agent-hub 0.12.42 → 0.12.43

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.
@@ -1045,7 +1045,11 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1045
1045
  - Snapshots are taken inside the state transition, before the peer is handed its
1046
1046
  prompt. Turn records (`turns` table in hub.db) keep the last `snapshots.keep`
1047
1047
  per peer. `DEFAULT_CONFIG` has snapshots off, like approvals.notify; a project
1048
- config turns them on.
1048
+ config turns them on. The daemon shares a monotonic 250 ms Git budget across a
1049
+ synchronous boundary burst, including repository/HEAD probes and end diffs. Each
1050
+ subprocess timeout uses only the remaining budget. Exhaustion or failed diff
1051
+ evidence records an unknown boundary, emits a diagnostic once per burst and
1052
+ grants no undo/attribution; I/O gets a turn before a new budget is available (#576).
1049
1053
  - `ahub undo` refuses the whole turn when any of its files changed since it
1050
1054
  ended (a file-by-file partial undo would leave a mixed state).
1051
1055
  - Codex: the method is `thread/revert {threadId, beforeTurnId}` (codex-cli
@@ -1332,7 +1336,7 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1332
1336
  - `hub_delivery_retry { delivery_id }` lets an authenticated recipient retry only its own current `needs_review` row. This option is offered only to transports with an independent tool turn (Claude channel/pull, Codex and Pi). The delivery-only local worker and headless ACP peers cannot start a tool turn through their own hold, so they do not advertise this tool and the daemon refuses a manual call before mutation. Their held hints ask the person to resolve the row; ordinary messages never bypass the hold. Kimi TUI remains pull-only and keeps the recipient option. An ephemeral delivery-only MCP marker controls exposure and hint copy only; the current adapter class is the authority. There is no action or reason input: `completed` and `discard` remain person-only. Any explicit retry in that recipient's lineage, including a person's retry, consumes this allowance; a redelivery held again stays for the person. The existing resolution transaction records `peer:<id>` and a fixed reason, keeps original envelopes unchanged and requeues them through the existing put-back path. A busy recipient may consent to this queue-only operation, but delivery waits for idle and all pause/recovery and active-delivery fences remain. The rendered clone reserves the existing journal body cap for the warning, clipping only the clone at a Unicode character boundary with an explicit cut marker when needed; originals remain unchanged. Jcode project MCP entries carry the same delivery-only exposure marker as other headless ACP peers, preserving their own configuration and peer identity rules. Final rendered output, after condensation and on pull or steer, starts with the fixed sanitized header `Hub redelivery warning: An earlier attempt of this delivery is uncertain; check for its effects before acting.` Explicit retry history is scoped by recipient and original envelope ids. A separate monotone consumed-proof projection records one proof id and warning provenance per original, leaving resolution audit rows unchanged. Explicit resolution and coalesced handoff inherit that proof atomically for every grouped non-context original; a rejected checkpoint consumes no fresh allowance. Hub presence originals carry mutable recall/shared context whose id survives merging, so their ids never participate in retry proof lookup, inheritance, warnings or unknown marking. A preface shared with earlier work cannot consume an unrelated new message's retry allowance; actual task/chat originals retain their consumed lineage. Safe-failure requeue, withdrawal of an earlier original, terminal-state changes, pruning, restart or older-writer audit replacement cannot renew the allowance. Older attempt-bearing rows backfill this projection on return. Recovery snapshots without history and unmappable pruned pre-feature retry records mark only their affected recipient/original ids unknown: those lineages require a person, while unrelated new messages keep their allowance. Tool results expose held metadata only (total count, ids, senders, kinds and age), at most 32 rows/4 KiB with explicit truncation; no body or task text enters the hint. These hints work independently of channel mode and do not wait behind the held queue (#434).
1333
1337
  - Local attach and idle model/route replacement require a reachable authenticated gateway, served models and successful minimal availability calls. Busy workers refuse replacement; validation failure leaves the current worker intact. Controlled recovery can reconstruct a manually paused local worker without gateway availability, but manual resume validates its restored choice before lifting the pause. Doctor checks the fixed model against the inventory.
1334
1338
  - Reviewer routing appends attached peers holding `roles.reviewer` after the review class's candidates. Without a reviewer, task results and console notices explain direct approval and the skipped candidates.
1335
- - Conflict detection includes `in_review` work. Completed overlapping snapshots that share files generate one concurrent-edit notice per task pair/file, containing both turn ids and attributing the changes to neither peer. Private tasks and sensitive file names remain redacted.
1339
+ - Conflict detection includes `in_review` work. Completed overlapping snapshots that share files compute one concurrent-edit change set across the completed overlapping turns and publish at most one conditional notice per affected task, containing both turn ids and attributing the changes to neither peer. Per-task-pair events remain research evidence, while notice persistence grows linearly in the affected tasks (#576). A bounded, per-worktree HEAD reflog cursor classifies moves appended during the turn. Incoming checkout, merge, pull and proven fast-forward reset paths are excluded from attributed changes and undo paths; normal peer commit, amend and cherry-pick paths remain attributed, as do sequencer pick/reword/edit/squash/fixup/continue/finish paths under native command prefixes. The start checkout of rebase or pull --rebase is incoming; overlapping replay paths are ambiguous, not attributed as upstream work. Paths still dirty between end HEAD and the end workspace snapshot are never excluded (including autostash and checkout -m). Independent working-tree edits remain attributed. Fast-forward reset follows this incoming policy even if its descendant target contains commits made by the peer elsewhere. Missing, rotated, truncated, unrecognized or ambiguous move evidence is unknown, never an exclusion; paths shared by own and incoming moves are ambiguous. A HEAD change during snapshot capture makes that capture unknown. Status probes the checkout branch once per synchronous burst, then screens the cached raw branch through the current privacy policy for every rendering; the cache ends before the next control turn. Pi terminal reconciliation reads an owner observation maintained by the existing 500 ms monitor and expiring after 1 s; fresh signature checks remain mandatory at tool/effect admission and teardown. Private tasks and sensitive file names remain redacted.
1336
1340
  - Pi assistant usage is forwarded through the authenticated bridge and recorded before settlement, without counting the same message both at `message_end` and `agent_end`.
1337
1341
  - An idle peer with no open owner/reviewer task and no queued or active delivery skips the quota checkpoint turn; the pause proceeds immediately and the log records the skip.
1338
1342
  - Control protocol 11 adds queue hold metadata; the current coordinator supports authenticated protocol 9, 10 and 11 recovery sources.
@@ -1542,9 +1546,10 @@ or Home/End sequences is that many key presses, never a paste. Home or `<` selec
1542
1546
  the first row of a panel and End or `>` the last (Home tops an open view); the
1543
1547
  Events panel follows new events while its selection is on the last row, keeps a
1544
1548
  row the person moved to, and opening it or changing its filter selects the newest
1545
- row (#441). In panels `w` opens the project dashboard (`ahub ui`) and `o` the pet
1546
- window (`ahub pet --window`) as a person's console command; the `:` line also
1547
- accepts `ui` and `pet` (#452). An Events row's header is
1549
+ row (#441). Outside the Peers panel, `w` opens the project dashboard (`ahub ui`);
1550
+ in Peers it opens the selected peer's WIP limit edit (#532 W1-W3). `o` opens the
1551
+ pet window (`ahub pet --window`) as a person's console command; the `:` line
1552
+ also accepts `ui` and `pet` (#452). An Events row's header is
1548
1553
  tokenized by the stream's own tokenizer on the same tab-flattened text, with the
1549
1554
  event's tone and kind; body lines stay plain, and selection changes only the
1550
1555
  `>` marker. The tail prints a peer's context reading once and repeats it only
@@ -1630,9 +1635,37 @@ console emits no box-drawing or other ambiguous-width glyph. The active tab is
1630
1635
  bracketed; the Approvals and Queue tabs count pending requests and
1631
1636
  `needs_review` deliveries. Peers, Approvals, Tasks and Queue are tables: a
1632
1637
  header row, columns two spaces apart sized with `Bun.stringWidth` from every
1633
- row (at most a third of the width), the last column taking the rest. Peers' LINK
1634
- column reads `tools-only` when the optional `toolsOnly` status field (#205) is
1635
- present. Zero counters read `-`, durations `45s`, `12m`, `3h05m` or `2d03h`, and
1638
+ row (at most a third of the width), the last column taking the rest. Peers'
1639
+ columns are PEER, STATE, MODE, LINK, Q, !, WIP, REVIEW, PAUSE, QUOTA and MODEL;
1640
+ MODE appears only when at least one peer has a non-`ask` mode. WIP reads
1641
+ `wip/limit` from the peer's `taskWip` entry, preserving zero (for example `0/2`),
1642
+ or `-` when absent. It uses the attention tone at `wip >= wipLimit`. WIP is
1643
+ optional at narrow widths and drops before PEER, STATE and QUOTA, with or
1644
+ without MODE. The Peers hint includes `w WIP limit`, shortened to `w WIP`
1645
+ through the existing width adaptation alongside `m permission mode`/`m mode`.
1646
+ Tasks has a `b` toggle between open work and backlog (#532 B1-B5), using the
1647
+ same public board response. Backlog rows sort by rank (missing last), then id;
1648
+ the heading counts them. Its columns are ID, PRI (priority/rank), RESERVED,
1649
+ DEPS (ready or unapproved dependency ids), CLASS, AGE and TITLE. PII titles stay
1650
+ stubs. Narrow widths drop metadata before ID, RESERVED and TITLE.
1651
+
1652
+ Backlog row keys prefill the existing person CLI: `p` is
1653
+ `backlog pull-ready <id> <planningRevision> <reason>`, `a` is `backlog plan`
1654
+ with a reservation JSON object, `e` is `backlog plan` with priority/rank/deps,
1655
+ and `c` is the existing flow-revision-bound `task cancel`. `n` in either Tasks
1656
+ view adds backlog work (`backlog add <class> <title...>`, or the command line's
1657
+ `task propose --backlog` with paths/deps). On proposed or in-progress open work,
1658
+ `R` returns an ownerless proposal with `backlog return <id>` or accepted,
1659
+ untouched work with the planning-revision-and-reason form. The hub retains its
1660
+ execution-evidence refusal. Row commands preserve id and revision through
1661
+ editing and confirmation, reject stale rows, and execute only after explicit
1662
+ confirmation; Esc sends nothing. Backlog mutations on the `:` line confirm too,
1663
+ while `backlog list` is read-only. Refusal notices preserve the hub's reason.
1664
+ There is no new write path or actor authority; person CLI and planner revision
1665
+ checks remain authoritative. Hints follow the selected row's available actions.
1666
+
1667
+ LINK reads `tools-only` when the optional `toolsOnly` status field (#205) is
1668
+ present. Other zero counters read `-`, durations `45s`, `12m`, `3h05m` or `2d03h`, and
1636
1669
  resets and pauses are relative. Detail views and the Approvals panel's request
1637
1670
  block put labels at column 0 and values one column past the longest label; a
1638
1671
  list puts `- ` before each item, and every other line of a value (its own
@@ -3231,7 +3264,14 @@ later turn; a running turn retains its capture decision and is never stopped.
3231
3264
 
3232
3265
  The console's sixth Settings tab reads the same registry as the dashboard. It edits
3233
3266
  through the authenticated daemon client: value, preview, nonempty bounded reason,
3234
- then explicit y; undo also previews and confirms. Stale preview replies cannot arm a
3267
+ then explicit y; undo also previews and confirms. In the Peers tab, `w` enters
3268
+ this same editor in place on Peers for `tasks.wip_limits.<peer>`, prefilled
3269
+ with the selected peer's current limit. Bounds (currently 0..32), the `raises` risk label and
3270
+ `applies: live` come from the setting definition. A missing `taskWip` entry
3271
+ produces a notice and opens no edit. Esc cancels without a write; `6`, then
3272
+ `u` invokes the existing Settings undo preview/confirmation operation. The shortcut creates no
3273
+ write path or authority: Settings remain person-only and `ahub settings` keeps
3274
+ its agent-shell refusal. Stale preview replies cannot arm a
3235
3275
  write, and pasted confirmation text never executes one. The daemon accepts additive
3236
3276
  settings_reload and undo-preview requests only from console roles. The dashboard's
3237
3277
  setting_reload action uses its existing authenticated session; manager forwarding
@@ -0,0 +1,50 @@
1
+ # 0.12.43 release verification
2
+
3
+ This record covers release preparation and source review. It does not claim publication, installation, a running-hub restart or a live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.12.43, control protocol 16 (unchanged; no control message added), from merged main. The person directed on 2026-10-21 to finish the open PRs, merge and ship.
8
+
9
+ - Hot paths under HEAD movement (#576, PR #608): snapshot Git calls in one burst share a 250 ms budget, an exhausted or failed boundary is unknown, concurrent-edit notices go once per affected task, and HEAD moves are classified from the HEAD reflog so incoming checkouts, merges and fast-forwards are excluded while a peer's own commits, amends, rebase replays and cherry-picks stay attributed. Scratch burst: `status` 21.3 s -> 0.27 s, false conflicts 144 -> 0, CPU 13 s -> 0.37 s.
10
+ - Restore mapping retry (#599, PR #600), named-owner refusal (#601, PR #602), `ahub completion` (#603, PR #604), agy read facts for 1.4.2 and 1.4.3 (#555, PR #605), agy 1.4.3 T0 receipt with 1.4.3 kept unverified (#267, PR #606), console Peers WIP column and `w` (PR #615) and the Tasks backlog view and row actions (PR #620) (#532), manager open stops a losing spawn (#584, PR #614).
11
+ - Contributor gate: per-PR CI concurrency groups and `scripts/check.sh --changed` with a pinned test environment (#609, #610, PR #611); two timing-dependent tests fixed (#617, #618, PR #619); live PII screen calibration recorded (#198, PRs #613 and #616).
12
+ - Still open: the agy TUI first-turn tools gap (#607), the #612 attribution-cost follow-ups, #428 (a facts test that fails in a full Linux container run), and every live leg listed in [the 0.12.40 record](2026-10-20-0.12.40.md).
13
+
14
+ ## Reviews
15
+
16
+ | PR | Final head | Review | CI |
17
+ | --- | --- | --- | --- |
18
+ | #600 (#599) | 729ec0b6 | Reviewer read of both paths; terminal-recovery tests 27/0 run by the reviewer. codex-connector: no findings | exact head, success |
19
+ | #602 (#601) | da3cf17b | Author self-review plus two codex-connector findings (self-claim note, enforce-mode refusal) fixed and answered | exact head, success |
20
+ | #604 (#603) | 40795529 | Three codex-connector findings (fish empty word, placeholder values, positional subcommands) fixed and answered; live fish and bash checks | exact head, success |
21
+ | #605 (#555) | b07e7f02 | Reviewer read; changes requested once for agy 1.4.3 `read_file`; connector P1 on ranged reads answered; agy tests 16/0 | exact head, success |
22
+ | #606 (#267) | 2ddcc915 | Reviewer read of the receipt; reference fix requested and applied | exact head, success |
23
+ | #608 (#576) | 4e6adb6e | Three review rounds, each with an independent read-only reviewer probing real git 2.56 reflog forms; R3 no Important finding; follow-ups in #612 | exact head, success |
24
+ | #611 (#609, #610) | 9af81d4b | Author review; first head was an invalid workflow (`queue: max` with `cancel-in-progress`), fixed; two connector findings on `--changed` fixed | exact head, success |
25
+ | #613 (#198) | 994d02cc | Docs only. A connector thread on the mutable model alias was answered after merge and fixed in #616 | exact head, success |
26
+ | #614 (#584) | b330ad32 | Reviewer codex approved; merger read the manager change | exact head, success |
27
+ | #615 (#532) | 0fb5c8c8 | Reviewer read; one change request (declared-instance WIP keys) fixed; `--changed` 463/0 and a full Linux exact-commit gate green except #428 | exact head, success (one rerun of the #618 flake) |
28
+ | #616 (#198) | 200adb70 | Served-model recording; connector finding on the probe cache fixed and answered | exact head, success |
29
+ | #620 (#532) | 42272f93 | Reviewer read: row actions bound to id and planning/flow revision, rechecked at Enter and y, no new write path; 37 new tests. codex-connector: no findings | exact head, success |
30
+ | #619 (#617, #618) | 534d9ebf | Reviewer read; macOS 20/20 and 100/100 reruns by the author | exact head, success |
31
+
32
+ ## Document review
33
+
34
+ Thirteen documents were stale against the 0.12.42 stamps. Each was checked against `git log <stamp>..HEAD` over its paths by a read-only reviewer per group, and the edits were applied on the release branch. All thirteen are restamped to the release branch commit `53743efd`, which includes #616 and #620.
35
+
36
+ | Document | Result | Checked claims and correction |
37
+ | --- | --- | --- |
38
+ | `README.md` | corrected | Status 0.12.43; agy read facts and 1.4.3 second-turn tools; console WIP limits; verification link. |
39
+ | `docs/quickstart.md` | corrected | Install pin `v0.12.43`; "Upgrading to 0.12.43"; plugin restart names 0.12.43; agy 1.4.3 second-turn note. |
40
+ | `docs/operations.md` | corrected | Version line, protocol 16 range, upgrade commands; restore mapping re-read (#599); unknown turn boundaries and reflog HEAD-move rules (#576); named-owner refusal (#601); agy read facts and 1.4.3 receipt (#555, #267); per-PR CI groups and `--changed` (#609, #610); per-instance WIP keys (#532). |
41
+ | `docs/security.md` | corrected | Snapshot budget (250 ms per burst, 10 s per call, unknown on exhaustion) (#576); a losing manager open stops its spawn (#584). |
42
+ | `src/cli/help.ts` | corrected | `task assign` says a peer that cannot take the task is refused with the reason (#601); `ahub completion` already listed; no other command, subcommand or flag changed. |
43
+ | `docs/agent-notes/daemon.md` | corrected | Settings editor bound to its opening panel incl. the Peers `w` shortcut (#532); console backlog row actions bound to id and revision (#532, PR #620); restore mapping re-read (#599); `openManager` keeps only the answering manager (#584). |
44
+ | `docs/agent-notes/adapters.md` | corrected | agy read decoding and Pre/PostToolUse facts (#555). |
45
+ | `docs/agent-notes/tests.md` | corrected | Captures yield an I/O turn when a test is not about the snapshot budget (#617). |
46
+ | `docs/agent-notes/README.md`, `benchmarks.md`, `budget.md`, `bus.md`, `tasks.md` | no change | Rules checked against the diffs (#601 and #608 rules already added in their PRs); no contradiction. |
47
+
48
+ ## Release mechanics
49
+
50
+ Merge the release PR with a merge commit (the stamps name release-branch commits), run `node scripts/check-docs.mjs --require-fresh` on the merged main, then tag `v0.12.43`.
@@ -0,0 +1,65 @@
1
+ # Antigravity 1.4.3 T0 re-run (#267)
2
+
3
+ Status: the 1.4.2 P1 T0 receipt ([2026-10-19-agy-p1-t0.md](2026-10-19-agy-p1-t0.md)) was re-run on 2026-10-21 against `agy 1.4.3`. **1.4.3 is not marked verified.** Most observations match, but the native tool names and the model-facing `call_mcp_tool` schema changed, the unmodified 1.4.2 probes no longer reach the hub, and in the interactive TUI the plugin MCP server is attached lazily on the first prompt, so the first model turn has no hub tools. No code changed. The live TUI leg was done separately in #268 (see below).
4
+
5
+ ## Environment and isolation
6
+
7
+ - Observed CLI version: `agy 1.4.3`.
8
+ - Executable SHA-256: `836c446f6118140e86679ec6966a785eeedbf56b3711a8eb1f41947483e9802a` (the person's `~/.local/bin/agy`, unchanged before and after).
9
+ - Bun 1.4.3 ran the probes. The 1.4.2 receipt used Bun 1.4.2, so the plugin MCP server and hook scripts also ran on a newer Bun. This is a second variable, not separated from the agy change.
10
+ - Probe scripts are copies of the 1.4.2 scripts (`actual-hub.ts`, `native-hooks.ts`, `workspace-plugin*.ts`), re-pointed at a clean worktree of main. The originals were not edited. Adapted variants (below) are separate copies.
11
+ - Same isolation as the receipt: a new temporary HOME with its own `.gemini`, workspace and temporary directory per run, an explicit environment, the Seatbelt profile from `src/local/sandbox.ts` denying the person's home except the executable, and a loopback Gemini protocol fixture. No real account or cloud model call is part of this receipt. The scratch hub had memory, inference, snapshots and auto-start disabled and explicit agy roster admission.
12
+ - SHA-256 of the person's Gemini CLI files (`antigravity-cli/settings.json`, `config/config.json`, `config/hooks.json`, `config/mcp_config.json`, `config/skills.json`, legacy `settings.json`) taken before the first run and after the last run are equal. The probes' own before/after hash (a subset) was equal on every completed run. The project's `.agents/` directory was not written; owned process groups were stopped and temporary roots removed.
13
+ - A first pass ran the unmodified `actual-hub.ts` with the agent scratch directory (under the person's home) as the temporary directory. It loaded no plugin hooks and made no MCP call. That run is not execution evidence and is not used below. All receipt runs use the per-user temporary directory, as the 1.4.2 run did.
14
+ - Real-HOME contact: the agent ran `agy --version` and `agy --help` (print and exit, no session, no plugin load) under the person's real HOME at about 03:49 and 04:01 KST before switching to a fresh HOME for every other call. The person's `~/.gemini/antigravity-cli/log/` has a `/usage` print-mode run from cwd `/` at 04:01:38 KST (`cli-20261021_040138.log`). It is one of a file series at a five minute cadence (`:01:38`, `:56:38`, `:51:38`, `:41:38` and so on), started by something outside these probes, none of which was a probe call: the probes only started agy with a temporary HOME and `--log-file` in a temporary directory.
15
+ - Raw bounded receipts and scripts remain in the local scratch workspace; they are not public artifacts.
16
+
17
+ ## Per-item comparison with 1.4.2
18
+
19
+ | Item | 1.4.3 observation | vs 1.4.2 |
20
+ | --- | --- | --- |
21
+ | `--version` | prints `1.4.3`, exit 0 | same (new number) |
22
+ | `--help` flags | still advertises `--conversation`, `--continue`, `--dangerously-skip-permissions`, `--mode`, `--new-project`, `--input-format`/`--output-format` stream JSON; no `--new` | same for every listed flag. A full diff is not possible: the 1.4.2 binary is not retained |
23
+ | `--acp` refusal | exit 2, `flags provided but not defined: -acp`; no ACP subcommand in the list | same. Refusal of the tested flag only, as before |
24
+ | `--new` | exit 2, undefined flag | same |
25
+ | Workspace plugin discovery | `.agents/plugins/agent-hub/` is loaded without install or global write; server name `agent-hub_agent-hub`; the unprefixed name still fails with `tool ... is not enabled for server agent-hub`; `agy mcp list` still prints `No MCP servers configured.` | same |
26
+ | Plugin-local `hooks.json` | the six hooks run from `.agents/plugins/agent-hub/hooks.json` | same |
27
+ | Hook stdin shared fields | `conversationId`, `workspacePaths`, `transcriptPath`, `artifactDirectoryPath`, `modelName`; transcript `transcript_full.jsonl` | same |
28
+ | `PreInvocation`/`PostInvocation` fields | adds `initialNumSteps`, `invocationNum` | same |
29
+ | Tool hook stdin | `toolCall: { name, args }`, `stepIdx`; `PostToolUse` has empty `error` on success; tool hooks still need `matcher: ".*"` with a nested `hooks` array | same shape |
30
+ | Native read tool in the tool hook | `read_file` with `Path`, `StartLine`, `EndLine` | **changed** (1.4.2: `view_file` with `AbsolutePath`) |
31
+ | `injectSteps` | `SessionStart` and `PreInvocation` `ephemeralMessage` accepted and shown to the model | same |
32
+ | Pre-tool `{ "decision": "allow" }` | accepted for the tested calls | same (still no override proof) |
33
+ | `Stop` fields | `executionNum`, `terminationReason` (`NO_TOOL_CALL`), `error`, `fullyIdle` | same |
34
+ | `call_mcp_tool` arguments | `ServerName`, `ToolName`, `Arguments` only. `toolSummary` and `toolAction` are now rejected: `additional properties 'toolAction', 'toolSummary' not allowed` | **changed** (1.4.2 required both) |
35
+ | Model-facing tool declarations | 15 tools: `read_file`, `write_file`, `edit_file`, `search_web` replace `view_file`, `write_to_file`, `replace_file_content`, `read_url_content`. A call to `view_file` fails with `unknown tool` although the stream-JSON `init` event still lists 60 tools including `view_file` | **changed** (1.4.2: 16 tools) |
36
+ | `init` event | 60 tools, `permission_mode: request-review`; no name added or removed | same |
37
+ | Unmodified 1.4.2 probes | every call that used `toolSummary`/`toolAction` or `view_file` is rejected before execution. `native-hooks.ts`: no `PreToolUse`/`PostToolUse` rows, no `tools/call` at the fixture MCP. `actual-hub.ts`: the single `hub_inbox` call is rejected, nonce not seen by the provider | **changed** |
38
+ | Actual scratch-hub `hub_inbox` with nonce, probes adapted to the 1.4.3 schema | exit 0, empty stderr, `hub_inbox` called once through the shipped bundle and the workspace plugin, the random nonce held only in the scratch bus preface appeared in the following fixture-provider request, all six hooks ran from the plugin-local file, native output SHA-256 `f1970aa687f0661c23e0f84924f2ff603d5f1c9edf9f718cc5e435448c862f3c` | same outcome, but only after fixture adaptation |
39
+
40
+ Adaptation used for the passing 1.4.3 runs: drop `toolSummary` and `toolAction` from `call_mcp_tool` arguments, and use `read_file` with `Path` for the native read. Nothing else was changed in the fixtures.
41
+
42
+ ## Late plugin MCP start: print mode versus the interactive TUI
43
+
44
+ The task owner saw hub tools missing on an early first turn of the person's own `ahub agy` TUI on 1.4.3 and present about ten minutes later in the same process. The isolated runs reproduce that as a startup-order effect, not a permanent failure. Times are seconds after spawn, taken from fixture timestamps (provider requests and the MCP fixture log), same Seatbelt profile and plugin files, fresh HOME each run:
45
+
46
+ - **Print mode (`-p`)**: the plugin MCP server is started during the first turn. The first provider request is the title generation (0 tools). The first agent request already carries 15 tools including `call_mcp_tool`, `list_resources` and `read_resource`, and the tool call succeeds.
47
+ - **Interactive TUI**: the plugin MCP server is not started at launch and not during the onboarding or idle time. In four runs (30 s, 13 s, 3 s and 3 s of idle after the trust prompt) it was started only when the first prompt was submitted: `server/discover`, `initialize` and `tools/list` ran in the same second as the first prompt. The first agent request after that prompt carried 12 tools and no `call_mcp_tool`. The next agent request carried 15 tools including `call_mcp_tool`, and every later request kept them. In the runs that sent prompts in quick succession that next request came 0.4 s to 1.7 s after the first prompt. A single-prompt run never made a second agent request, so whether the attach is driven by elapsed time or by the next request cannot be separated at this resolution. Waiting longer before the first prompt did not change the first request (30 s idle: still 12 tools).
48
+ - Consequence: on 1.4.3 the TUI attaches the plugin MCP server lazily on the first prompt, and the first model turn has no hub tools. A prompt that needs a hub tool on that turn fails. The tools are available from the following turn. The roughly ten minute delay seen in the person's session is longer than anything measured here and is not explained (the person's HOME also loads a global MCP server and global hooks, which the isolated HOME does not). In the isolated runs a second prompt had the tools within two seconds.
49
+ - Plugin hooks fire in the TUI from `SessionStart`, including `PreToolUse` and `PostToolUse` for the MCP call.
50
+ - The agy log prints `hooks_manager ... loaded 0 named hooks from 0 hooks.json file(s)` in both modes while the plugin hooks run, and print mode adds a line for the plugin file. The line is not a reliable signal of plugin hook loading.
51
+ - A fresh HOME makes the TUI stop at onboarding (theme, terms, workspace trust). The runs answered those with key presses (Enter, Down, Right, Enter, Enter).
52
+ - **Not established: whether 1.4.2 behaved the same.** The 1.4.2 receipt never observed the TUI and no 1.4.2 binary is retained anywhere on this machine, so time-to-tools cannot be measured there. This is a 1.4.3 mode difference, not a proven 1.4.2 to 1.4.3 regression.
53
+
54
+ ## Consequences
55
+
56
+ - `agyVersionGate` is not changed: 1.4.3 keeps the one-line stderr warning and is not added to the verified set.
57
+ - `src/adapters/agy/hooks.ts` classifies a native read only when `toolCall.name === "view_file"` and `args.AbsolutePath` is a string. On 1.4.3 the native read is `read_file` with `Path`, so the decoder passes the call through as an unclassified tool and no `Read` fact is attributed. This is a follow-up for a separate task. It was not changed here because the task forbids code changes when anything differs.
58
+ - Any hub-side prompt or fixture that tells the model to call `call_mcp_tool` with `toolSummary`/`toolAction` is invalid on 1.4.3.
59
+ - The launcher's plugin namespace assumption (`agent-hub_agent-hub`) still holds.
60
+ - Do not rely on `hub_*` tools on the first TUI turn of a 1.4.3 session. A launcher or preface that needs the hub on turn one has to wait for the second turn (or the person submits any first prompt). The live TUI leg (#268, agy's own TUI on hub 0.12.42 and agy 1.4.3, which superseded the cancelled #251) passed `hub_inbox`, 42 hub tools, a `hub_send` round trip and a board read from the second turn on, and also had no hub tools on its first turn, as the timing above explains. The close-cleanup check is still open.
61
+
62
+ ## Unchanged limits
63
+
64
+ - Permission prompt mappings, `allow` versus native approval prompts, Stop continuation and post-invocation termination overrides remain unverified. Keep the peer unmanaged.
65
+ - Not a sandbox claim for the proposed agy peer. Documentation alone is not native proof.
@@ -0,0 +1,14 @@
1
+ # PII screen calibration (issue #198), 2026-10-20
2
+
3
+ - Screen: campus gateway, model coding
4
+ - Served model (as the gateway reported it): qwen3.8-flash-next
5
+ - Fixtures: 78 synthetic items (38 positives, 40 hard negatives), test/fixtures/pii-screen.json
6
+ - Recall (pii labels only): 100.0%; bound 90.0%, fixed before measuring: met
7
+ - Recall as handled (pii or unknown): 100.0%
8
+ - Precision (pii labels): 100.0%
9
+ - Unknown rate: 0.0%
10
+ - Hard negatives held as PII (pii or unknown): 0.0%
11
+ - Category agreement on true positives: 100.0%
12
+ - Latency: p50 246 ms, p95 301 ms, max 869 ms
13
+ - Missed positives: none
14
+ - Negatives not cleared: none
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
5
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
14
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "ba26148f52511968910d5596572bc8ad7b5c6a3a",
21
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -35,7 +35,7 @@
35
35
  ]
36
36
  },
37
37
  "docs/quickstart.md": {
38
- "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
38
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
39
39
  "paths": [
40
40
  "package.json",
41
41
  "src/",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "docs/agent-notes/adapters.md": {
46
- "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
46
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
47
47
  "paths": [
48
48
  "src/adapters/",
49
49
  "src/pi/",
@@ -69,7 +69,7 @@
69
69
  ]
70
70
  },
71
71
  "docs/agent-notes/benchmarks.md": {
72
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
72
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
73
73
  "paths": [
74
74
  "scripts/benchmarks/",
75
75
  "test/benchmarks/",
@@ -81,7 +81,7 @@
81
81
  ]
82
82
  },
83
83
  "docs/agent-notes/budget.md": {
84
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
84
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
85
85
  "paths": [
86
86
  "src/hub/budget.ts",
87
87
  "src/cli/statusline-tee.ts",
@@ -98,7 +98,7 @@
98
98
  ]
99
99
  },
100
100
  "docs/agent-notes/bus.md": {
101
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
101
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
102
102
  "paths": [
103
103
  "src/hub/bus.ts",
104
104
  "src/hub/envelope.ts",
@@ -113,7 +113,7 @@
113
113
  ]
114
114
  },
115
115
  "docs/agent-notes/daemon.md": {
116
- "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
116
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
117
117
  "paths": [
118
118
  "src/hub/daemon.ts",
119
119
  "src/hub/control-client.ts",
@@ -188,7 +188,7 @@
188
188
  ]
189
189
  },
190
190
  "docs/agent-notes/tasks.md": {
191
- "verifiedAgainst": "644f9a45a05e8dcaa8adf2a3febc1bedc9bd331b",
191
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
192
192
  "paths": [
193
193
  "src/hub/tasks.ts",
194
194
  "src/hub/board.ts",
@@ -215,7 +215,7 @@
215
215
  ]
216
216
  },
217
217
  "docs/agent-notes/tests.md": {
218
- "verifiedAgainst": "ba26148f52511968910d5596572bc8ad7b5c6a3a",
218
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
219
219
  "paths": [
220
220
  "test/",
221
221
  "scripts/check.sh",
@@ -233,7 +233,7 @@
233
233
  ]
234
234
  },
235
235
  "docs/agent-notes/README.md": {
236
- "verifiedAgainst": "e57b1936bd234fad65aded000c921f5c9d7a3718",
236
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
237
237
  "paths": [
238
238
  "src/cli/console-state.ts",
239
239
  "src/cli/attention.ts",
@@ -242,7 +242,7 @@
242
242
  ]
243
243
  },
244
244
  "src/cli/help.ts": {
245
- "verifiedAgainst": "6f01288071848891e54ae1dda6ba87f365a060c8",
245
+ "verifiedAgainst": "53743efd865a91f1f0766f5af0613c298ac3d7b9",
246
246
  "paths": [
247
247
  "src/cli/main.ts",
248
248
  "src/cli/launch.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.12.42",
3
+ "version": "0.12.43",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.12.42",
3
+ "version": "0.12.43",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",
@@ -4300,7 +4300,7 @@ function ownerStillAlive(pid, signature) {
4300
4300
  return false;
4301
4301
  }
4302
4302
  }
4303
- var PiToolSettlementFailure, PiPromptNotStarted, PiPromptUncertain, PiPeer;
4303
+ var PiToolSettlementFailure, PiPromptNotStarted, PiPromptUncertain, TERMINAL_OWNER_MAX_AGE_MS = 1000, PiPeer;
4304
4304
  var init_pi = __esm(() => {
4305
4305
  init_launch();
4306
4306
  init_envelope();
@@ -4381,6 +4381,7 @@ var init_pi = __esm(() => {
4381
4381
  ownerPid;
4382
4382
  ownerSignature;
4383
4383
  ownerMonitor;
4384
+ terminalOwnerObservation;
4384
4385
  tuiExit;
4385
4386
  resolveTuiExit;
4386
4387
  tuiCommands = new Map;
@@ -4409,7 +4410,7 @@ var init_pi = __esm(() => {
4409
4410
  if (this.opts.mode === "headless")
4410
4411
  return this.state === "idle" || !this.proc || this.proc.exitCode !== null || this.proc.signalCode !== null;
4411
4412
  if (this.state === "idle")
4412
- return this.ownerClaimed && !!this.ownerPid && processSignature(this.ownerPid) === this.ownerSignature;
4413
+ return this.ownerClaimed && !!this.ownerPid && this.readOwnerSignature(this.ownerPid) === this.ownerSignature;
4413
4414
  return this.stopping && !this.ownerClaimed && (!this.ownerPid || !ownerStillAlive(this.ownerPid, this.ownerSignature));
4414
4415
  }
4415
4416
  get replacementReady() {
@@ -4633,6 +4634,7 @@ var init_pi = __esm(() => {
4633
4634
  return Response.json({ decisions, ...reservation ? { reservation } : {} });
4634
4635
  }
4635
4636
  async startImpl() {
4637
+ this.terminalOwnerObservation = undefined;
4636
4638
  this.stopping = false;
4637
4639
  this.rosterDetached = false;
4638
4640
  this.rosterUnownedRun = false;
@@ -4687,7 +4689,7 @@ var init_pi = __esm(() => {
4687
4689
  return Response.json({ ok: false, error: "Pi session identity is missing" }, { status: 400 });
4688
4690
  if (body.type === "session_start" && this.ownerClaimed && (body.ownerToken !== this.ownerToken || body.pid !== this.ownerPid || body.signature !== this.ownerSignature || this.sessionId && body.sessionId !== this.sessionId || this.sessionFile && body.sessionFile !== this.sessionFile))
4689
4691
  return Response.json({ ok: false, error: "Pi session owner or identity already claimed" }, { status: 409 });
4690
- if (body.type === "session_start" && (!Number.isInteger(body.pid) || !body.signature || processSignature(body.pid) !== body.signature))
4692
+ if (body.type === "session_start" && (!Number.isInteger(body.pid) || !body.signature || this.readOwnerSignature(body.pid) !== body.signature))
4691
4693
  return Response.json({ ok: false, error: "Pi process identity is not verified" }, { status: 409 });
4692
4694
  this.handleBridgeEvent(body);
4693
4695
  return Response.json({ ok: true });
@@ -4743,7 +4745,7 @@ var init_pi = __esm(() => {
4743
4745
  this.sessionId = state.data.sessionId;
4744
4746
  this.sessionFile = state.data.sessionFile;
4745
4747
  if (!this.ownerClaimed) {
4746
- const pid = this.proc?.pid, signature = pid ? processSignature(pid) : undefined;
4748
+ const pid = this.proc?.pid, signature = pid ? this.readOwnerSignature(pid) : undefined;
4747
4749
  if (!pid || !signature)
4748
4750
  throw new Error("Pi headless owner process identity is not verified");
4749
4751
  this.ownerPid = pid;
@@ -4757,11 +4759,27 @@ var init_pi = __esm(() => {
4757
4759
  this.started = true;
4758
4760
  this.setState("idle");
4759
4761
  }
4762
+ readOwnerSignature(pid) {
4763
+ return (this.opts.signature ?? processSignature)(pid);
4764
+ }
4760
4765
  verifiedOwner() {
4761
- return this.ownerClaimed && !!this.ownerPid && !!this.ownerSignature && processSignature(this.ownerPid) === this.ownerSignature;
4766
+ return this.ownerClaimed && !!this.ownerPid && !!this.ownerSignature && this.readOwnerSignature(this.ownerPid) === this.ownerSignature;
4767
+ }
4768
+ rememberTerminalOwner() {
4769
+ if (!this.ownerClaimed || !this.ownerPid || !this.ownerSignature)
4770
+ return;
4771
+ this.terminalOwnerObservation = {
4772
+ pid: this.ownerPid,
4773
+ signature: this.ownerSignature,
4774
+ sessionId: this.sessionId,
4775
+ sessionFile: this.sessionFile,
4776
+ ownerToken: this.ownerToken,
4777
+ checkedAt: performance.now()
4778
+ };
4762
4779
  }
4763
4780
  terminalOwner(_launchId) {
4764
- if (this.opts.mode !== "tui" || !this.started || this.stopping || this.rosterDetached || !this.verifiedOwner() || !this.sessionId || !this.sessionFile || this.opts.sessionId && this.opts.sessionId !== this.sessionId || this.opts.sessionFile && this.opts.sessionFile !== this.sessionFile)
4781
+ const observed = this.terminalOwnerObservation;
4782
+ if (this.opts.mode !== "tui" || !this.started || this.stopping || this.rosterDetached || this.state === "offline" || !this.ownerClaimed || !observed || performance.now() - observed.checkedAt > TERMINAL_OWNER_MAX_AGE_MS || observed.pid !== this.ownerPid || observed.signature !== this.ownerSignature || observed.sessionId !== this.sessionId || observed.sessionFile !== this.sessionFile || observed.ownerToken !== this.ownerToken || !this.sessionId || !this.sessionFile || this.opts.sessionId && this.opts.sessionId !== this.sessionId || this.opts.sessionFile && this.opts.sessionFile !== this.sessionFile)
4765
4783
  return;
4766
4784
  const generation = this.sessionGeneration;
4767
4785
  return generation && this.ownerPid ? { generation, nativePid: this.ownerPid } : undefined;
@@ -4780,6 +4798,7 @@ var init_pi = __esm(() => {
4780
4798
  async detachForRoster() {
4781
4799
  if (this.opts.mode !== "tui")
4782
4800
  throw new Error("Pi roster detach requires a personal TUI");
4801
+ this.terminalOwnerObservation = undefined;
4783
4802
  this.rosterDetached = true;
4784
4803
  this.rosterEpoch++;
4785
4804
  this.setState("offline");
@@ -4841,6 +4860,7 @@ var init_pi = __esm(() => {
4841
4860
  if (reason !== undefined)
4842
4861
  this.requestedStopReason = reason;
4843
4862
  this.rosterEpoch++;
4863
+ this.terminalOwnerObservation = undefined;
4844
4864
  this.stopping = true;
4845
4865
  for (const id of this.activeDeliveryIds)
4846
4866
  this.delivery({ id, state: "needs_review", reason: reason ?? "Pi session stopped before settlement" });
@@ -5159,6 +5179,7 @@ var init_pi = __esm(() => {
5159
5179
  this.opts.log?.(`[${this.id}] Pi session identity mismatch`);
5160
5180
  return;
5161
5181
  }
5182
+ this.rememberTerminalOwner();
5162
5183
  this.startOwnerMonitor();
5163
5184
  if (this.opts.mode === "tui") {
5164
5185
  this.started = true;
@@ -5177,7 +5198,7 @@ var init_pi = __esm(() => {
5177
5198
  if (this.opts.mode === "tui")
5178
5199
  this.reportExit(exit);
5179
5200
  }
5180
- if (event.type === "tool_abort" && this.agentRunning && this.ownerClaimed && this.ownerPid && this.ownerSignature && processSignature(this.ownerPid) === this.ownerSignature && event.sessionId === this.sessionId && event.generation === this.budgetGeneration && typeof event.toolCallId === "string" && event.toolCallId.length > 0 && event.toolCallId.length <= 512) {
5201
+ if (event.type === "tool_abort" && this.agentRunning && this.ownerClaimed && this.ownerPid && this.ownerSignature && this.readOwnerSignature(this.ownerPid) === this.ownerSignature && event.sessionId === this.sessionId && event.generation === this.budgetGeneration && typeof event.toolCallId === "string" && event.toolCallId.length > 0 && event.toolCallId.length <= 512) {
5181
5202
  const pending = this.pendingToolGenerations.get(event.toolCallId);
5182
5203
  if (pending && pending.generation === event.generation)
5183
5204
  pending.abort.abort();
@@ -5416,6 +5437,7 @@ var init_pi = __esm(() => {
5416
5437
  } catch {}
5417
5438
  }
5418
5439
  fail(error) {
5440
+ this.terminalOwnerObservation = undefined;
5419
5441
  const approvalStopReason = this.approvalStopReason;
5420
5442
  this.abortTools();
5421
5443
  this.agentRunning = false;
@@ -5445,24 +5467,39 @@ var init_pi = __esm(() => {
5445
5467
  this.clearOwnerMonitor();
5446
5468
  if (this.opts.mode !== "tui" || !this.ownerPid || !this.ownerSignature)
5447
5469
  return;
5448
- this.ownerMonitor = setInterval(() => {
5449
- if (!this.ownerPid || ownerStillAlive(this.ownerPid, this.ownerSignature))
5450
- return;
5451
- const exit = this.exitMetadata("owner_lost");
5452
- this.stopping = true;
5453
- this.ownerClaimed = false;
5454
- this.clearOwnerMonitor();
5455
- this.resolveTuiExit?.();
5456
- this.resolveTuiExit = undefined;
5457
- for (const pending of this.tuiCommands.values())
5458
- pending.reject(new Error("Pi owner exited"));
5459
- this.tuiCommands.clear();
5460
- this.tuiQueue = [];
5461
- this.fail(new Error("Pi owner process exited without session_shutdown"));
5462
- this.reportExit(exit);
5463
- }, 500);
5470
+ this.ownerMonitor = setInterval(() => this.checkOwner(), 500);
5464
5471
  this.ownerMonitor.unref?.();
5465
5472
  }
5473
+ checkOwner() {
5474
+ if (!this.ownerPid) {
5475
+ this.terminalOwnerObservation = undefined;
5476
+ return;
5477
+ }
5478
+ const current = this.readOwnerSignature(this.ownerPid);
5479
+ if (current !== undefined && current === this.ownerSignature) {
5480
+ this.rememberTerminalOwner();
5481
+ return;
5482
+ }
5483
+ this.terminalOwnerObservation = undefined;
5484
+ if (current === undefined) {
5485
+ try {
5486
+ process.kill(this.ownerPid, 0);
5487
+ return;
5488
+ } catch {}
5489
+ }
5490
+ const exit = this.exitMetadata("owner_lost");
5491
+ this.stopping = true;
5492
+ this.ownerClaimed = false;
5493
+ this.clearOwnerMonitor();
5494
+ this.resolveTuiExit?.();
5495
+ this.resolveTuiExit = undefined;
5496
+ for (const pending of this.tuiCommands.values())
5497
+ pending.reject(new Error("Pi owner exited"));
5498
+ this.tuiCommands.clear();
5499
+ this.tuiQueue = [];
5500
+ this.fail(new Error("Pi owner process exited without session_shutdown"));
5501
+ this.reportExit(exit);
5502
+ }
5466
5503
  clearOwnerMonitor() {
5467
5504
  if (this.ownerMonitor)
5468
5505
  clearInterval(this.ownerMonitor);
@@ -7868,7 +7905,7 @@ var init_hub_tools = __esm(() => {
7868
7905
  CONDUCTOR_TOOLS = [
7869
7906
  tool("hub_status", "Inspect public team state, holds, quota windows, task counts and pending approval peer/tool/age. Only the configured conductor may use this tool.", {}),
7870
7907
  tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
7871
- tool("hub_task_assign", "Name an owner (default role) or reviewer. Only the conductor may name a reviewer through this tool. A proposer may redirect an unaccepted proposed task's owner; a task that still waits reserves that owner. Pull-only peers receive work when named or when they claim it. With an explicit capabilities list, the conductor needs assign capability for either role, the proposer only to give ownership to another peer.", { id, peer: str2, role: { type: "string", enum: ["owner", "reviewer"] } }, ["id", "peer"]),
7908
+ tool("hub_task_assign", "Name an owner (default role) or reviewer. Only the conductor may name a reviewer through this tool. A proposer may redirect an unaccepted proposed task's owner; a task that still waits reserves that owner. Pull-only peers receive work when named while attached or when they claim it; a peer that cannot take the task is refused with the reason, and the task is unchanged. With an explicit capabilities list, the conductor needs assign capability for either role, the proposer only to give ownership to another peer.", { id, peer: str2, role: { type: "string", enum: ["owner", "reviewer"] } }, ["id", "peer"]),
7872
7909
  tool("hub_task_escalate", "Escalate a task through the normal task flow, as the conductor. Requires assign capability when explicitly listed.", { id }, ["id"]),
7873
7910
  tool("hub_peer_start", "Start local, kimi, qwen, jcode, pi, claude or codex in the start mode a person set for it; you cannot choose the mode. A headless peer starts at once. A TUI peer (claude, and pi and codex by default) opens in a terminal with a fixed command; when the hub cannot open a terminal the call fails with the command for the person to run.", { peer: { type: "string", enum: CONDUCTOR_START_PEERS } }, ["peer"]),
7874
7911
  tool("hub_peer_hold", "Hold a peer's deliveries as the conductor. This hold is separate from the person's hold and budget pauses.", { peer: str2 }, ["peer"]),
@@ -8562,7 +8599,9 @@ function decodeAgyHook(event, value) {
8562
8599
  if ((event === "PreToolUse" || event === "PostToolUse") && (!call || typeof call.name !== "string" || call.name.length > 128 || !record3(call.args)))
8563
8600
  return;
8564
8601
  const native = call?.args;
8565
- const observedRead = call?.name === "view_file" && typeof native?.AbsolutePath === "string";
8602
+ const readPath = call?.name === "view_file" ? native?.AbsolutePath : call?.name === "read_file" ? native?.Path : undefined;
8603
+ const observedRead = typeof readPath === "string";
8604
+ const range = observedRead && Number.isSafeInteger(native?.StartLine) && native.StartLine > 0 ? { offset: native.StartLine, ...Number.isSafeInteger(native?.EndLine) && native.EndLine >= native.StartLine ? { limit: native.EndLine - native.StartLine + 1 } : {} } : {};
8566
8605
  const phase = { SessionStart: "session", PreInvocation: "start", PostInvocation: "post", PreToolUse: "pre", PostToolUse: "post", Stop: "stop" }[event];
8567
8606
  return {
8568
8607
  event,
@@ -8571,7 +8610,7 @@ function decodeAgyHook(event, value) {
8571
8610
  transcriptPath: value.transcriptPath,
8572
8611
  workspacePaths: value.workspacePaths,
8573
8612
  tool: observedRead ? "Read" : typeof call?.name === "string" ? call.name : "",
8574
- input: observedRead ? { file_path: native.AbsolutePath } : native ?? {},
8613
+ input: observedRead ? { file_path: readPath, ...range } : native ?? {},
8575
8614
  ...Number.isSafeInteger(value.stepIdx) && value.stepIdx >= 0 ? { toolUseId: `${value.conversationId}:${value.stepIdx}` } : {},
8576
8615
  ...Number.isSafeInteger(value.invocationNum) && value.invocationNum >= 0 ? { invocation: value.invocationNum } : {},
8577
8616
  ...event === "Stop" ? { fullyIdle: value.fullyIdle, terminationReason: typeof value.terminationReason === "string" ? value.terminationReason : "" } : {}
@@ -18427,7 +18466,7 @@ init_spec_read();
18427
18466
  // package.json
18428
18467
  var package_default = {
18429
18468
  name: "@staix/agent-hub",
18430
- version: "0.12.42",
18469
+ version: "0.12.43",
18431
18470
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
18432
18471
  license: "MIT",
18433
18472
  type: "module",
@@ -19,10 +19,15 @@ export function decodeAgyHook(event: string, value: unknown): NativeFactsInput |
19
19
  if ((event === "PreToolUse" || event === "PostToolUse") && (!call || typeof call.name !== "string" || call.name.length > 128 || !record(call.args))) return;
20
20
  const native = call?.args as Record<string, unknown> | undefined;
21
21
  // Only the observed native read is classified. Unobserved edits never receive a scoped grant or effect attribution.
22
- const observedRead = call?.name === "view_file" && typeof native?.AbsolutePath === "string";
22
+ // 1.4.2 names it view_file(AbsolutePath); 1.4.3 renamed it read_file(Path, optional StartLine/EndLine).
23
+ const readPath = call?.name === "view_file" ? native?.AbsolutePath : call?.name === "read_file" ? native?.Path : undefined;
24
+ const observedRead = typeof readPath === "string";
25
+ // A line range is a partial read: carry it as offset/limit so it never counts as a whole-file view.
26
+ const range = observedRead && Number.isSafeInteger(native?.StartLine) && (native!.StartLine as number) > 0
27
+ ? { offset: native!.StartLine as number, ...(Number.isSafeInteger(native?.EndLine) && (native!.EndLine as number) >= (native!.StartLine as number) ? { limit: (native!.EndLine as number) - (native!.StartLine as number) + 1 } : {}) } : {};
23
28
  const phase = ({ SessionStart: "session", PreInvocation: "start", PostInvocation: "post", PreToolUse: "pre", PostToolUse: "post", Stop: "stop" } as const)[event as typeof EVENTS[number]];
24
29
  return { event: event as NativeFactsInput["event"], phase, sessionId: value.conversationId, transcriptPath: value.transcriptPath,
25
- workspacePaths: value.workspacePaths as string[], tool: observedRead ? "Read" : typeof call?.name === "string" ? call.name : "", input: observedRead ? { file_path: native!.AbsolutePath } : native ?? {},
30
+ workspacePaths: value.workspacePaths as string[], tool: observedRead ? "Read" : typeof call?.name === "string" ? call.name : "", input: observedRead ? { file_path: readPath, ...range } : native ?? {},
26
31
  ...(Number.isSafeInteger(value.stepIdx) && (value.stepIdx as number) >= 0 ? { toolUseId: `${value.conversationId}:${value.stepIdx}` } : {}),
27
32
  ...(Number.isSafeInteger(value.invocationNum) && (value.invocationNum as number) >= 0 ? { invocation: value.invocationNum as number } : {}),
28
33
  ...(event === "Stop" ? { fullyIdle: value.fullyIdle as boolean, terminationReason: typeof value.terminationReason === "string" ? value.terminationReason : "" } : {}) };