@staix/agent-hub 0.33.0 → 0.34.1
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 +17 -0
- package/README.md +2 -2
- package/docs/dogfood.md +2 -1
- package/docs/operations.md +80 -9
- package/docs/quickstart.md +26 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +5 -2
- package/docs/verification/2026-11-07-0.34.0.md +71 -0
- package/docs/verification/2026-11-07-0.34.1.md +54 -0
- package/docs/verification/2026-11-07-compat-receipt-audit-1248.md +256 -0
- package/docs/verified.json +9 -9
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1 -1
- package/src/adapters/definitions/native-cli-claude-setup.ts +14 -5
- package/src/cli/autonomy-homes.ts +12 -3
- package/src/cli/claude-homes.ts +5 -5
- package/src/cli/claude-profile-hook.ts +38 -9
- package/src/cli/install-paths.ts +71 -0
- package/src/cli/main.ts +20 -1
- package/src/cli/recovery-package.ts +15 -7
- package/src/cli/runtime-version-warning.ts +8 -6
- package/src/cli/setup-homes.ts +13 -10
- package/src/cli/terminal-recovery.ts +59 -3
- package/src/cli/upgrade-interactive.ts +29 -6
- package/src/cli/upgrade-output.ts +3 -2
- package/src/cli/upgrade-runtime.ts +34 -4
- package/src/cli/upgrade.ts +3 -1
- package/src/hub/handoff.ts +1 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# COMPAT receipt audit for CI automation (#1248 T0)
|
|
2
|
+
|
|
3
|
+
Date: 2026-11-07. Task #761. Receipt pointers were read from `src/adapters/compat.ts` at `3440e7b10dc6b4537d56541774436aa4f7d49a92`. The data source is [manifest.json](../../scripts/compat-receipts/manifest.json). This audit runs no agent CLI, installer, model, TUI or workflow and changes no verified version.
|
|
4
|
+
|
|
5
|
+
`autoAdmit` means policy eligibility after every required leg and artifact binding passes. It is not an implemented driver or a current pass. Missing work is not-run. One Mac arm64 job can repeat an eligible id's complete procedure, including its portable checks; the Linux category does not require an extra matrix job. Native macOS arm64 qualification is retained; a portable Linux fixture or source comparison does not qualify a Linux-only rerun. Qwen explicitly rejected Linux-only evidence. Optional, explicitly unverified model/TUI follow-ups do not become new admission requirements.
|
|
6
|
+
|
|
7
|
+
Source comparison can produce hashes and diffs on Linux. Changed security/contract regions still require scoped human review: diff availability alone is never a passing review leg. T1 must report such work as not-run until reviewed.
|
|
8
|
+
|
|
9
|
+
## Per-id scheduling and eligibility
|
|
10
|
+
|
|
11
|
+
| COMPAT id | Model-free repeat | Remaining required scope | Installation | autoAdmit |
|
|
12
|
+
| --- | --- | --- | --- | --- |
|
|
13
|
+
| `pi` | Linux host permission/version fixtures; Mac RPC extension, SDK and tool receipts | Mac arm64 native | npm exact | `true` |
|
|
14
|
+
| `claude-implementer` | Mac Write/Bash/rewrite transport and wrapper controls, synthetic provider | Mac arm64 native | npm exact; native-installer receipt boundary | `true` |
|
|
15
|
+
| `codex-review` | Linux source comparison; Mac production metadata and native sandbox controls | Person-authorized model-backed tool call | npm exact | `false` |
|
|
16
|
+
| `kimi-review` | Linux source comparison; Mac private config/broker/native allowlist controls | Mac arm64 native | GitHub asset + SHA-256 | `true` |
|
|
17
|
+
| `jcode` | Linux source comparison; Mac serve/ACP/MCP and fixed-stream steering | Mac arm64 native | GitHub asset + SHA256SUMS | `true` |
|
|
18
|
+
| `agy` | Mac CLI, synthetic MCP/hooks and Seatbelt controls | Interactive TUI/launcher nonce; no public install source | Unavailable | `false` |
|
|
19
|
+
| `qwen` | Mac ACP/permission and install replay; recorded Linux run is additional | Mac arm64 repetition required | npm exact | `true` |
|
|
20
|
+
| `qwen-dual` | Version/file-source checks are partial only | Real gateway model and PTY; OS not recorded | npm exact; historical artifact gap | `false` |
|
|
21
|
+
|
|
22
|
+
The macOS requirement follows each receipt's native artifact/host boundary; the manifest does not claim those native procedures have passed on Linux. `qwen-dual`'s OS remains unspecified because its current receipt does not name one. `codex-review` stays false even with successful model-free preparation; its separately authorized 0.167.0 model-backed attempt has an unknown outcome. `agy` stays false independently of model-free sublegs because there is no public install source.
|
|
23
|
+
|
|
24
|
+
## Installation evidence and boundaries
|
|
25
|
+
|
|
26
|
+
Install methods describe exact future candidate acquisition, with no global installer. npm candidates must bind to their exact-version `dist.integrity`; release candidates must verify the versioned checksum and GitHub asset digest before bounded extraction. Reference hashes in the manifest are observed metadata or receipt bindings, not a new execution result.
|
|
27
|
+
|
|
28
|
+
- Claude 2.1.319 was observed through the native installer while npm lagged. The exact npm version is now publicly available and its SRI was read without downloading or executing it. That does not equate its tarball with the receipt's native binary SHA.
|
|
29
|
+
- Jcode's receipt identifies an installed release build. The v0.99.1 release API now supplies `jcode-macos-aarch64.tar.gz`, an asset SHA-256 and `SHA256SUMS`; that future acquisition recipe is not retrospective archive-execution proof.
|
|
30
|
+
- Kimi's reference receipt already binds its official macOS arm64 archive to the GitHub digest and sibling checksum.
|
|
31
|
+
- Codex's native binary hashes and qwen-dual's changing fnm global install are not npm SRI evidence. Public exact-version npm metadata was read separately.
|
|
32
|
+
- No npm/GitHub method is invented for agy. Its unavailable install method makes an automated run not-run.
|
|
33
|
+
|
|
34
|
+
## pi
|
|
35
|
+
|
|
36
|
+
Current receipt: [2026-11-03-pi-150-compat.md](2026-11-03-pi-150-compat.md). `autoAdmit: true`. The admitted procedure is model-free; real inference and interactive coexistence are explicitly unverified follow-ups.
|
|
37
|
+
|
|
38
|
+
### Recorded receipt legs
|
|
39
|
+
|
|
40
|
+
- **native-identity**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Native identity".
|
|
41
|
+
- **cli-rpc-session**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Real CLI extension load and RPC session identity".
|
|
42
|
+
- **sdk-registration**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Installed SDK extension registration".
|
|
43
|
+
- **tool-replay**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Managed tool callback and receipt replay".
|
|
44
|
+
- **failed-tool**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Failed tool result".
|
|
45
|
+
- **zero-provider-requests**: `macos-runner`, required, runner OS `macos`. [## Results](2026-11-03-pi-150-compat.md): "Zero requests reached the loopback provider".
|
|
46
|
+
- **host-permission-modes**: `linux-ci`, required, runner OS `linux, macos`. [## Results](2026-11-03-pi-150-compat.md): "Host permission modes".
|
|
47
|
+
- **version-admission**: `linux-ci`, required, runner OS `linux, macos`. [## Results](2026-11-03-pi-150-compat.md): "Version admission".
|
|
48
|
+
- **isolated-home-cleanup**: `macos-runner`, required, runner OS `macos`. [## Identity and isolation](2026-11-03-pi-150-compat.md): "9551 entries, identical.".
|
|
49
|
+
|
|
50
|
+
### Explicitly unverified follow-ups
|
|
51
|
+
|
|
52
|
+
- **real-inference-tools-quota**: `account-or-model`, not required by the current admission receipt. [## Not verified](2026-11-03-pi-150-compat.md): "Account-backed inference, a model-generated tool request".
|
|
53
|
+
- **interactive-coexistence**: `tui-desktop`, not required by the current admission receipt. [## Not verified](2026-11-03-pi-150-compat.md): "interactive TUI coexistence.".
|
|
54
|
+
- **native-linux-intel**: `linux-ci`, not required by the current admission receipt. [## Not verified](2026-11-03-pi-150-compat.md): "Linux and Intel macOS.".
|
|
55
|
+
|
|
56
|
+
## claude-implementer
|
|
57
|
+
|
|
58
|
+
Current receipt: [2026-11-06-claude-implementer-2.1.319.md](2026-11-06-claude-implementer-2.1.319.md). `autoAdmit: true`. Permission transport and wrapper controls use a synthetic provider, with no real account or model required.
|
|
59
|
+
|
|
60
|
+
### Recorded receipt legs
|
|
61
|
+
|
|
62
|
+
- **write-allow**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
63
|
+
- **write-deny**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
64
|
+
- **write-missing-tool**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
65
|
+
- **write-hook-deny**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
66
|
+
- **write-hook-allow**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
67
|
+
- **write-mcp-error**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
68
|
+
- **write-disconnect**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
69
|
+
- **write-pending**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
70
|
+
- **write-deadline**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Write transport (allow, deny, missing tool, hook deny/allow, MCP error, disconnect, pending, deadline)".
|
|
71
|
+
- **bash-allow**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
72
|
+
- **bash-deny**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
73
|
+
- **bash-missing-tool**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
74
|
+
- **bash-hook-deny**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
75
|
+
- **bash-hook-allow**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
76
|
+
- **bash-mcp-error**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
77
|
+
- **bash-disconnect**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
78
|
+
- **bash-pending**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
79
|
+
- **bash-deadline**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash transport, production server and tool names, `tool_use_id` required".
|
|
80
|
+
- **bash-rewrite-allow**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash `updatedInput` command replacement (allow, deny)".
|
|
81
|
+
- **bash-rewrite-deny**: `macos-runner`, required, runner OS `macos`. [## Runs](2026-11-06-claude-implementer-2.1.319.md): "Bash `updatedInput` command replacement (allow, deny)".
|
|
82
|
+
- **wrapper-cwd**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Persistent-shell control reports `sub` twice".
|
|
83
|
+
- **wrapper-root**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Fresh rewritten input executes the fixed root".
|
|
84
|
+
- **wrapper-executable**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Target identity equals the absolute host-selected executable".
|
|
85
|
+
- **wrapper-poison-env**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Injected native GIT_DIR and RIPGREP_CONFIG_PATH are absent".
|
|
86
|
+
- **wrapper-host-env**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Target HOME/PATH/TERM and peer identity equal the host fixture values".
|
|
87
|
+
- **wrapper-tmp**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Owned TMPDIR replaces native state".
|
|
88
|
+
- **wrapper-replay**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Same ticket's second execution returns the fixed refusal".
|
|
89
|
+
- **wrapper-effect-once**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Effect counter remains exactly 1 after replay".
|
|
90
|
+
- **wrapper-quoted-paths**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Spaces and apostrophes in root/executable paths".
|
|
91
|
+
- **wrapper-cleanup**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Native exit 0, host stopped, no remaining live native group members".
|
|
92
|
+
- **wrapper-binary-fence**: `macos-runner`, required, runner OS `macos`. [## Native observations](2026-10-26-headless-claude-shell-context.md): "Binary unchanged across the case".
|
|
93
|
+
- **upstream-impact-review**: `linux-ci`, required, runner OS `linux, macos`. [## Upstream changes since 2.1.318](2026-11-06-claude-implementer-2.1.319.md): "This receipt therefore rests on observed behavior only".
|
|
94
|
+
|
|
95
|
+
### Explicitly unverified follow-ups
|
|
96
|
+
|
|
97
|
+
- **real-provider-model**: `account-or-model`, not required by the current admission receipt. [## Evidence boundaries](2026-11-06-claude-implementer-2.1.319.md): "no real provider authentication, model tool selection".
|
|
98
|
+
- **person-approval**: `tui-desktop`, not required by the current admission receipt. [## Evidence boundaries](2026-11-06-claude-implementer-2.1.319.md): "person approval interaction".
|
|
99
|
+
- **native-task-resume-binding**: `account-or-model`, not required by the current admission receipt. [## Evidence boundaries](2026-11-06-claude-implementer-2.1.319.md): "task-session resume or production-daemon task binding is proved".
|
|
100
|
+
- **host-seatbelt-policy**: `macos-runner`, not required by the current admission receipt. [## Host fixtures and ceilings](2026-10-26-headless-claude-shell-context.md): "The executable task-code profile is host policy verified separately by Darwin fixtures.".
|
|
101
|
+
- **native-internal-timeout**: `macos-runner`, not required by the current admission receipt. [## Observations](2026-10-23-headless-claude-permissions.md): "native internal timeout not established".
|
|
102
|
+
|
|
103
|
+
## codex-review
|
|
104
|
+
|
|
105
|
+
Current receipt: [2026-11-06-codex-0167-reviewer-isolation.md](2026-11-06-codex-0167-reviewer-isolation.md). `autoAdmit: false`. Admission requires a successful separately authorized model-backed hub tool call, even when every model-free preparation leg passes.
|
|
106
|
+
|
|
107
|
+
### Recorded receipt legs
|
|
108
|
+
|
|
109
|
+
- **source-impact**: `linux-ci`, required, runner OS `linux, macos`. [## Source identity and re-read regions](2026-11-06-codex-0167-reviewer-isolation.md): "Each of the seven files read for 0.166.0".
|
|
110
|
+
- Changed source: not-run until the changed security/contract regions have been reviewed; never infer a pass from diff availability.
|
|
111
|
+
- **tag-binding**: `linux-ci`, required, runner OS `linux, macos`. [## Source identity and re-read regions](2026-11-06-codex-0167-reviewer-isolation.md): "annotated tag object".
|
|
112
|
+
- **version-negative**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "Refused only at version admission".
|
|
113
|
+
- **empty-home-prepare**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "empty homes | All production preparation legs pass".
|
|
114
|
+
- **ambient-prepare**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "synthetic ambient config | All production preparation legs pass".
|
|
115
|
+
- **control**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "Control 0.166.0, empty homes".
|
|
116
|
+
- **model-free-mcp-inventory-disabled-ambient**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "model-free-mcp-inventory-disabled-ambient".
|
|
117
|
+
- **hub-tools-exact-allowlist**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "hub-tools-exact-allowlist".
|
|
118
|
+
- **plugins-and-hooks-disabled**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "plugins-and-hooks-disabled".
|
|
119
|
+
- **legacy-notify-disabled**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "legacy-notify-disabled".
|
|
120
|
+
- **apps-disabled-effective-feature-readback**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "apps-disabled-effective-feature-readback".
|
|
121
|
+
- **web-search-disabled**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "web-search-disabled".
|
|
122
|
+
- **project-untrusted**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "project-untrusted".
|
|
123
|
+
- **native-public-read-private-deny-write-deny**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "native-public-read-private-deny-write-deny".
|
|
124
|
+
- **native-glob-and-symlink-private-deny**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "native-glob-and-symlink-private-deny".
|
|
125
|
+
- **portable-core-name-and-state-deny**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "portable-core-name-and-state-deny".
|
|
126
|
+
- **standard-host-credential-roots-denied**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "standard-host-credential-roots-denied".
|
|
127
|
+
- **binary-config-stable**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "binary-config-stable".
|
|
128
|
+
- **binary-hash-once-metadata-fence**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "binary-hash-once-metadata-fence".
|
|
129
|
+
- **session-ephemeral**: `macos-runner`, required, runner OS `macos`. [## Native production preparation](2026-11-06-codex-0167-reviewer-isolation.md): "session-ephemeral".
|
|
130
|
+
- **model-backed-hub-call**: `account-or-model`, required, runner OS `macos`. [## Reviewers](../agent-notes/adapters.md): "before marking a reviewer backend/version verified, record a successful model-backed hub tool call".
|
|
131
|
+
|
|
132
|
+
### Explicitly unverified follow-ups
|
|
133
|
+
|
|
134
|
+
- **effective-network-web**: `account-or-model`, not required by the current admission receipt. [## Compatibility and evidence boundaries](2026-11-06-codex-0167-reviewer-isolation.md): "The network setting is configured disabled without a separate probe.".
|
|
135
|
+
- **default-profile-selection**: `account-or-model`, not required by the current admission receipt. [## Compatibility and evidence boundaries](2026-11-06-codex-0167-reviewer-isolation.md): "exec selecting the named default profile".
|
|
136
|
+
- **native-linux**: `linux-ci`, not required by the current admission receipt. [## Compatibility and evidence boundaries](2026-11-06-codex-0167-reviewer-isolation.md): "No native Linux result is claimed.".
|
|
137
|
+
|
|
138
|
+
## kimi-review
|
|
139
|
+
|
|
140
|
+
Current receipt: [2026-11-06-kimi-251-reviewer-compat.md](2026-11-06-kimi-251-reviewer-compat.md). `autoAdmit: true`. The required native allowlist/broker controls use fixed loopback replies and synthetic credentials, without real inference.
|
|
141
|
+
|
|
142
|
+
### Recorded receipt legs
|
|
143
|
+
|
|
144
|
+
- **prepare**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Production preparation, exact version/current/config, replace environment".
|
|
145
|
+
- **inventory**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Exactly two MCP tools, empty subagents and skills, config mode 0600".
|
|
146
|
+
- **broker-refusals**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Wrong task, third tool, missing authorization and revoked broker refusals".
|
|
147
|
+
- **native-tool-loop**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Actual native two-tool loop, fixed loopback responses".
|
|
148
|
+
- **write-absent**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Injected Write absent from inventory and native tool-not-found result".
|
|
149
|
+
- **write-positive-control**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Explicit Write positive control".
|
|
150
|
+
- **environment-decoy**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Trust-workspace environment decoy removed from launch environment".
|
|
151
|
+
- **cleanup**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "Original synthetic config/credential bytes, owned-group and temporary cleanup".
|
|
152
|
+
- **network-guard-control**: `macos-runner`, required, runner OS `macos`. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "A separate preload positive control blocked a".
|
|
153
|
+
- **source-review**: `linux-ci`, required, runner OS `linux, macos`. [## Source review](2026-11-06-kimi-251-reviewer-compat.md): "Model-free native exact-two-tool inventories across all fixed-response requests are still required".
|
|
154
|
+
- Changed source: not-run until the changed security/contract regions have been reviewed; never infer a pass from diff availability.
|
|
155
|
+
|
|
156
|
+
### Explicitly unverified follow-ups
|
|
157
|
+
|
|
158
|
+
- **real-oauth-provider**: `account-or-model`, not required by the current admission receipt. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "login/provider readiness, real OAuth refresh".
|
|
159
|
+
- **model-quota**: `account-or-model`, not required by the current admission receipt. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "model
|
|
160
|
+
behavior/quality and quota remain unverified.".
|
|
161
|
+
- **tui-coexistence**: `tui-desktop`, not required by the current admission receipt. [## Isolated production and native controls](2026-11-06-kimi-251-reviewer-compat.md): "attached-TUI coexistence".
|
|
162
|
+
- **other-platforms**: `linux-ci`, not required by the current admission receipt. [## Evidence and decision](2026-11-06-kimi-251-reviewer-compat.md): "Other platforms, future versions and model-backed
|
|
163
|
+
behavior are not inferred.".
|
|
164
|
+
|
|
165
|
+
## jcode
|
|
166
|
+
|
|
167
|
+
Current receipt: [2026-10-26-jcode-0.99.1.md](2026-10-26-jcode-0.99.1.md). `autoAdmit: true`. ACP, serve, MCP and running-turn steering use an isolated fixed-stream loopback provider; the real TUI/model follow-ups are not current admission legs.
|
|
168
|
+
|
|
169
|
+
### Recorded receipt legs
|
|
170
|
+
|
|
171
|
+
- **isolation**: `macos-runner`, required, runner OS `macos`. [## Isolation](2026-10-26-jcode-0.99.1.md): "Every process ran under an isolated `HOME` and `JCODE_HOME`".
|
|
172
|
+
- **serve-cleanup**: `macos-runner`, required, runner OS `macos`. [## Contract legs (idle server, no model)](2026-10-26-jcode-0.99.1.md): "The process group is gone after SIGTERM.".
|
|
173
|
+
- **acp-initialize-new-load-mode-ceilings**: `macos-runner`, required, runner OS `macos`. [## Contract legs (idle server, no model)](2026-10-26-jcode-0.99.1.md): "`session/load` returns `configOptions` and `models` only, with no `sessionId`.".
|
|
174
|
+
- **mcp-owned-environment**: `macos-runner`, required, runner OS `macos`. [## Contract legs (idle server, no model)](2026-10-26-jcode-0.99.1.md): "its environment held exactly `AGENTHUB_PEER_ID` and `AGENTHUB_LAUNCH_ID`".
|
|
175
|
+
- **telemetry**: `macos-runner`, required, runner OS `macos`. [## Contract legs (idle server, no model)](2026-10-26-jcode-0.99.1.md): "no telemetry file appeared in the isolated home.".
|
|
176
|
+
- **active-turn-soft-interrupt**: `macos-runner`, required, runner OS `macos`. [## Steering leg (the 0.99.0 check, now on a running turn)](2026-10-26-jcode-0.99.1.md): "No real provider, key or model was involved.".
|
|
177
|
+
- **source-comparison**: `linux-ci`, required, runner OS `linux, macos`. [## Source comparison v0.99.0..v0.99.1](2026-10-26-jcode-0.99.1.md): "232 commits, 300 files, read only through the GitHub compare API (nothing built).".
|
|
178
|
+
- Changed source: not-run until the changed security/contract regions have been reviewed; never infer a pass from diff availability.
|
|
179
|
+
|
|
180
|
+
### Explicitly unverified follow-ups
|
|
181
|
+
|
|
182
|
+
- **real-provider-model-tool-call**: `account-or-model`, not required by the current admission receipt. [## Not verified](2026-10-26-jcode-0.99.1.md): "A real account, provider or model, and a model turn that calls a hub tool".
|
|
183
|
+
- **tui-repeat**: `tui-desktop`, not required by the current admission receipt. [## Not verified](2026-10-26-jcode-0.99.1.md): "The TUI form (`jcode --resume`)".
|
|
184
|
+
- **linux-intel-second-instance**: `linux-ci`, not required by the current admission receipt. [## Not verified](2026-10-26-jcode-0.99.1.md): "A second instance running beside the base peer, and Linux or Intel macOS builds.".
|
|
185
|
+
|
|
186
|
+
## agy
|
|
187
|
+
|
|
188
|
+
Current receipt: [2026-11-03-agy-1.4.12-launcher-nonce.md](2026-11-03-agy-1.4.12-launcher-nonce.md). `autoAdmit: false`. No public installation source is available to the watcher; the current procedure also requires interactive TUI/launcher controls.
|
|
189
|
+
|
|
190
|
+
### Recorded receipt legs
|
|
191
|
+
|
|
192
|
+
- **isolation-seatbelt-cleanup**: `macos-runner`, required, runner OS `macos`. [## Environment and isolation](2026-11-03-agy-1.4.12-launcher-nonce.md): "No account and no cloud model call.".
|
|
193
|
+
- **cli-contract**: `macos-runner`, required, runner OS `macos`. [# Antigravity 1.4.12 actual-hub nonce leg through the `ahub agy` launcher (#888)](2026-11-03-agy-1.4.12-launcher-nonce.md): "passed the CLI surface, the synthetic plugin MCP and hook contract and the normal TUI close".
|
|
194
|
+
- **synthetic-plugin-mcp-hooks**: `macos-runner`, required, runner OS `macos`. [## Synthetic plugin MCP and hook contract: passed](2026-11-03-agy-1.4.12.md): "## Synthetic plugin MCP and hook contract: passed".
|
|
195
|
+
- **normal-tui-close**: `tui-desktop`, required, runner OS `macos`. [## Result](2026-11-03-agy-1.4.12-launcher-nonce.md): "0, after `/exit` and Enter; agy closed by itself and printed its resume line".
|
|
196
|
+
- **launcher-actual-hub-nonce**: `tui-desktop`, required, runner OS `macos`. [## Result](2026-11-03-agy-1.4.12-launcher-nonce.md): "the nonce from the scratch bus preface appeared in a provider request after the tool call".
|
|
197
|
+
|
|
198
|
+
### Explicitly unverified follow-ups
|
|
199
|
+
|
|
200
|
+
- **real-account-model-permission**: `account-or-model`, not required by the current admission receipt. [## Not verified](2026-11-03-agy-1.4.12-launcher-nonce.md): "A real account, a real model's tool choices and agy's own permission prompts".
|
|
201
|
+
- **ide-linux-other-tui-tools**: `tui-desktop`, not required by the current admission receipt. [## Not verified](2026-11-03-agy-1.4.12-launcher-nonce.md): "`hub_send` and board calls through the TUI, the IDE form and Linux.".
|
|
202
|
+
|
|
203
|
+
## qwen
|
|
204
|
+
|
|
205
|
+
Current receipt: [2026-11-06-qwen-0268-acp.md](2026-11-06-qwen-0268-acp.md). `autoAdmit: true`. The admitted ACP/permission procedure uses no prompts or real model; macOS arm64 repetition remains required.
|
|
206
|
+
|
|
207
|
+
### Recorded receipt legs
|
|
208
|
+
|
|
209
|
+
- **artifact-private-install-integrity**: `macos-runner`, required, runner OS `macos`. [## Artifact and isolation](2026-11-06-qwen-0268-acp.md): "npm installed into a new private prefix with scratch HOME, cache and tmp, empty user/global npm configs".
|
|
210
|
+
- **reviewed-optional-lifecycle-replay**: `macos-runner`, required, runner OS `macos`. [## Artifact and isolation](2026-11-06-qwen-0268-acp.md): "Fresh ACP and permission probes after the replay passed again.".
|
|
211
|
+
- **native-acp-auth-session-modes**: `macos-runner`, required, runner OS `macos`. [## Native ACP, no prompts](2026-11-06-qwen-0268-acp.md): "ACP prompts sent: 0.".
|
|
212
|
+
- **actual-published-permission-handler**: `macos-runner`, required, runner OS `macos`. [## Actual published permission handler](2026-11-06-qwen-0268-acp.md): "without a model-generated tool turn.".
|
|
213
|
+
- **preload-network-cleanup**: `macos-runner`, required, runner OS `macos`. [## Artifact and isolation](2026-11-06-qwen-0268-acp.md): "This is preload evidence, not kernel network isolation.".
|
|
214
|
+
- **native-macos-repetition**: `macos-runner`, required, runner OS `macos`. [## Actual macOS arm64 repetition](2026-11-06-qwen-0268-acp.md): "The Linux-only receipt did not prove admission on this Mac.".
|
|
215
|
+
- **additional-linux**: `linux-ci`, recorded, not required for admission, runner OS `linux`. [# Qwen Code 0.26.8 ACP receipt](2026-11-06-qwen-0268-acp.md): "The earlier Linux x86_64 run is additional evidence.".
|
|
216
|
+
|
|
217
|
+
### Explicitly unverified follow-ups
|
|
218
|
+
|
|
219
|
+
- **real-model-microphone-tui-intel**: `account-or-model`, not required by the current admission receipt. [## Not verified](2026-11-06-qwen-0268-acp.md): "Microphone functionality, Intel macOS, a real login/provider, model-generated permission turns, model behavior, token/quota behavior and TUI qualification.".
|
|
220
|
+
|
|
221
|
+
## qwen-dual
|
|
222
|
+
|
|
223
|
+
Current receipt: [2026-10-25-qwen-dual-output-t0.md](2026-10-25-qwen-dual-output-t0.md). `autoAdmit: false`. The current receipt uses a real gateway model and an interactive PTY; model-free automation cannot admit a version.
|
|
224
|
+
|
|
225
|
+
### Recorded receipt legs
|
|
226
|
+
|
|
227
|
+
- **real-model**: `account-or-model`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "`flashnext/qwen3.8-flash-next` through the campus gateway".
|
|
228
|
+
- **pty-harness**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "the TUI ran on a pseudo-terminal (120x40).".
|
|
229
|
+
- **Q2-submit-stream-queue**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "queued, not interleaved".
|
|
230
|
+
- **Q3-permissions-native-enter-deny-ceiling**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "A tool that needs approval emits `control_request` `can_use_tool`".
|
|
231
|
+
- **Q4-known-watchdog-ceiling**: `tui-desktop`, recorded, not required for admission, runner OS `unspecified`. [# Qwen dual-output TUI amendment, 2026-10-25 (#286 Part 3)](../specs/2026-09-19-agent-hub-design.md): "Q2, Q3, Q5 and Q6 pass; Q4's limit is stated".
|
|
232
|
+
- Recorded accepted watchdog limitation, not a passing cancellation capability; preserve it in future receipts.
|
|
233
|
+
- **Q5-unread-fifo**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "With `--json-file` a FIFO that nobody opened for reading".
|
|
234
|
+
- **Q6-resume-no-replay**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "did not replay any earlier `submit`, and appended to the event file.".
|
|
235
|
+
- **Q7-version-identity**: `tui-desktop`, required, runner OS `unspecified`. [# Qwen dual-output TUI verification, 2026-10-25](2026-10-25-qwen-dual-output-t0.md): "`qwen --version` under the isolated environment: 0.26.1.".
|
|
236
|
+
|
|
237
|
+
### Explicitly unverified follow-ups
|
|
238
|
+
|
|
239
|
+
- None separately identified.
|
|
240
|
+
|
|
241
|
+
## How an automation-opened PR gets checks
|
|
242
|
+
|
|
243
|
+
Token choice remains with the person. This audit enables neither route. Current [GitHub trigger documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) describes approval-required runs for GITHUB_TOKEN-created opened/synchronize/reopened PR events; dispatch events remain an exception. This qualifies the issue's older no-trigger shorthand and does not establish unattended PR CI. This is documentation readback, not a live token experiment.
|
|
244
|
+
|
|
245
|
+
| Route | Benefit | Trade-off and integration work |
|
|
246
|
+
| --- | --- | --- |
|
|
247
|
+
| GITHUB_TOKEN plus explicit `check.yml` dispatch on `compat/<id>-<version>` | Avoids introducing a person-supplied App private key; dispatch can start the full check independently of PR-event approval | Publish job needs dispatch permission; capture and validate the run's exact head SHA. `check.yml` already accepts workflow_dispatch with normal inputs. Dispatch shares its existing check-main queue. It does not itself run the separate closing-refs or routing-receipt workflows; T2 must cover those gates or retain required approval. |
|
|
248
|
+
| Person-provided GitHub App installation token for branch/PR writes | Normal PR events can start all existing checks automatically | The person must provide a narrowly scoped installation and App credentials, with rotation/revocation ownership. Credentials belong only in the publish job, never the third-party package/harness job. Normal review and merge gates remain. |
|
|
249
|
+
|
|
250
|
+
The [workflow dispatch API](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event) accepts a branch/tag ref and requires Actions write permission for fine-grained tokens. Run evidence must bind to the PR's actual head, rather than merely a branch name. [GitHub App authentication](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow) documents creating an installation token from the App credentials.
|
|
251
|
+
|
|
252
|
+
Repository readback: `.github/workflows/check.yml` has workflow_dispatch, reads the dispatched SHA when there is no pull_request head, and uses check-main concurrency for ordinary dispatches. `.github/workflows/closing-refs.yml` and `.github/workflows/routing-receipt.yml` currently have only pull_request triggers. No workflow file changed here.
|
|
253
|
+
|
|
254
|
+
## Scope and handoff
|
|
255
|
+
|
|
256
|
+
T0 delivers data, provenance and a coverage test only. T1 still implements bounded isolated installation/probes and fail/not-run reporting; T2 still integrates the watcher, no-secret/read-only harness, artifact-only publisher and the person's chosen check-trigger mechanism. No token has been selected or provisioned, and no admission PR is auto-merged.
|
package/docs/verified.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"documents": {
|
|
4
4
|
"README.md": {
|
|
5
|
-
"verifiedAgainst": "
|
|
5
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
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": "
|
|
14
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
15
15
|
"paths": [
|
|
16
16
|
"src/",
|
|
17
17
|
"templates/"
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"docs/operations.md": {
|
|
21
|
-
"verifiedAgainst": "
|
|
21
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
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": "
|
|
38
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
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": "
|
|
46
|
+
"verifiedAgainst": "d31d7503fce542f37ed9cdacb3c40fc86786cb09",
|
|
47
47
|
"paths": [
|
|
48
48
|
"src/adapters/",
|
|
49
49
|
"src/pi/",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
]
|
|
133
133
|
},
|
|
134
134
|
"docs/agent-notes/daemon.md": {
|
|
135
|
-
"verifiedAgainst": "
|
|
135
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
136
136
|
"paths": [
|
|
137
137
|
"src/hub/daemon.ts",
|
|
138
138
|
"src/hub/control-client.ts",
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
]
|
|
244
244
|
},
|
|
245
245
|
"docs/agent-notes/tasks.md": {
|
|
246
|
-
"verifiedAgainst": "
|
|
246
|
+
"verifiedAgainst": "d31d7503fce542f37ed9cdacb3c40fc86786cb09",
|
|
247
247
|
"paths": [
|
|
248
248
|
"src/hub/tasks.ts",
|
|
249
249
|
"src/hub/board.ts",
|
|
@@ -291,7 +291,7 @@
|
|
|
291
291
|
]
|
|
292
292
|
},
|
|
293
293
|
"docs/agent-notes/tests.md": {
|
|
294
|
-
"verifiedAgainst": "
|
|
294
|
+
"verifiedAgainst": "fc75a18dc2fa92b8372c19371c23ac6088d21ca3",
|
|
295
295
|
"paths": [
|
|
296
296
|
"test/",
|
|
297
297
|
"scripts/check.sh",
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
]
|
|
330
330
|
},
|
|
331
331
|
"src/cli/help.ts": {
|
|
332
|
-
"verifiedAgainst": "
|
|
332
|
+
"verifiedAgainst": "d31d7503fce542f37ed9cdacb3c40fc86786cb09",
|
|
333
333
|
"paths": [
|
|
334
334
|
"src/cli/main.ts",
|
|
335
335
|
"src/cli/launch.ts",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.1",
|
|
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",
|
|
@@ -10248,7 +10248,7 @@ var package_default;
|
|
|
10248
10248
|
var init_package = __esm(() => {
|
|
10249
10249
|
package_default = {
|
|
10250
10250
|
name: "@staix/agent-hub",
|
|
10251
|
-
version: "0.
|
|
10251
|
+
version: "0.34.1",
|
|
10252
10252
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
10253
10253
|
license: "MIT",
|
|
10254
10254
|
type: "module",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { stablePackageRoot } from "../../cli/install-paths.ts";
|
|
3
5
|
import { realPath } from "../../hub/project.ts";
|
|
4
6
|
import { VERSION } from "../../version.ts";
|
|
5
7
|
|
|
@@ -53,11 +55,11 @@ export type PluginState =
|
|
|
53
55
|
| { state: "stale"; version: string; why: string; notices?: string[] };
|
|
54
56
|
|
|
55
57
|
const safeDisplayValue = (value: unknown): string | undefined =>
|
|
56
|
-
typeof value === "string" && value.length <= 4096 && !/[\
|
|
58
|
+
typeof value === "string" && value.length <= 4096 && value.isWellFormed() && !/[\p{Cc}\p{Cf}]/u.test(value) ? value : undefined;
|
|
57
59
|
|
|
58
60
|
function nonUserPluginNotices(plugins: InstalledPlugin[]): string[] {
|
|
59
61
|
return plugins.filter(plugin => plugin.id === PLUGIN && plugin.scope !== "user").map(plugin => {
|
|
60
|
-
const scope = plugin.scope
|
|
62
|
+
const scope = safeDisplayValue(plugin.scope) ?? "unknown-scope";
|
|
61
63
|
const path = safeDisplayValue(plugin.projectPath) ?? safeDisplayValue(plugin.installPath);
|
|
62
64
|
const version = safeDisplayValue(plugin.version) ?? "unknown";
|
|
63
65
|
return `${scope} plugin row preserved${path ? ` at ${path}` : ""} (version ${version}); setup never removes non-user scopes`;
|
|
@@ -90,14 +92,21 @@ export interface SetupStep {
|
|
|
90
92
|
* The next thing `ahub setup` has to do, or nothing. One step at a time: the state is read again after every step,
|
|
91
93
|
* because what a step leaves behind is Claude Code's business (removing a marketplace may uninstall its plugins).
|
|
92
94
|
*/
|
|
93
|
-
export function nextStep(plugins: InstalledPlugin[], marketplaces: Marketplace[], packageRoot: string): SetupStep | undefined {
|
|
95
|
+
export function nextStep(plugins: InstalledPlugin[], marketplaces: Marketplace[], packageRoot: string, personHome = homedir()): SetupStep | undefined {
|
|
94
96
|
const root = real(packageRoot);
|
|
95
97
|
const market = marketplaces.find((m) => m.name === MARKETPLACE);
|
|
96
98
|
if (market && real(market.path ?? "") !== root) {
|
|
97
|
-
|
|
99
|
+
const blockers = plugins.filter(plugin => plugin.id === PLUGIN && plugin.scope !== "user");
|
|
100
|
+
if (blockers.length) {
|
|
101
|
+
const display = (value: unknown) => JSON.stringify(safeDisplayValue(value) ?? "unknown");
|
|
102
|
+
const rows = blockers.map(plugin => `scope=${display(plugin.scope)}, projectPath=${display(plugin.projectPath)}, version=${display(plugin.version)}, installPath=${display(plugin.installPath)}`).join("; ");
|
|
103
|
+
const homeProject = blockers.some(plugin => safeDisplayValue(plugin.projectPath) !== undefined && real(plugin.projectPath!) === real(personHome));
|
|
104
|
+
const danger = homeProject ? ` Do not run 'claude plugin uninstall --scope project' from $HOME (${display(personHome)}): its project settings file ~/.claude/settings.json (${display(join(personHome, ".claude/settings.json"))}) is also the default home's user settings.` : "";
|
|
105
|
+
return { why: `the ${MARKETPLACE} marketplace points at another source; removing it may remove non-user plugin rows. Blocking rows: ${rows}. Hub-launched Claude sessions load their plugin from ~/.agenthub/releases (${display(join(personHome, ".agenthub/releases"))}), not from these plugin-cache rows. Standalone Claude sessions may still use them.${danger} Next: back up the selected Claude home's plugins/installed_plugins.json, review every listed row, and manually remove only rows confirmed stale and unused, preserving every other row; then rerun ahub setup. Setup never removes these rows automatically`, argv: [], blocked: true };
|
|
106
|
+
}
|
|
98
107
|
return { why: `the ${MARKETPLACE} marketplace points at ${market.path ?? "another source"}, not at this package`, argv: ["claude", "plugin", "marketplace", "remove", MARKETPLACE] };
|
|
99
108
|
}
|
|
100
|
-
if (!market) return { why: "register this package as a Claude Code plugin marketplace", argv: ["claude", "plugin", "marketplace", "add", root] };
|
|
109
|
+
if (!market) return { why: "register this package as a Claude Code plugin marketplace", argv: ["claude", "plugin", "marketplace", "add", stablePackageRoot(root)] };
|
|
101
110
|
const state = pluginState(plugins, root);
|
|
102
111
|
if (state.state === "missing") return { why: "install the user-scope channel plugin", argv: ["claude", "plugin", "install", PLUGIN, "--scope", "user"] };
|
|
103
112
|
// Uninstall rather than update: with an unchanged version number an update has nothing to do.
|
|
@@ -22,12 +22,21 @@ export async function usedClaudeHomes(registry: Registry, home: string, env: Nod
|
|
|
22
22
|
return { ...found, complete: found.complete && !notices.length, notices: [...notices, ...found.notices] };
|
|
23
23
|
}
|
|
24
24
|
export async function autonomyHomeStatus(registry: Registry, home: string, env: NodeJS.ProcessEnv, packageRoot: string, probe: ClaudeHomeProbe) {
|
|
25
|
-
const found = await usedClaudeHomes(registry, home, env)
|
|
25
|
+
const found = await usedClaudeHomes(registry, home, env);
|
|
26
|
+
// Doctor also inspects the existing default home; setup's selected write inventory stays unchanged.
|
|
27
|
+
const defaults = await discoverClaudeHomes({ home, env: { ...env, CLAUDE_CONFIG_DIR: undefined } });
|
|
28
|
+
// Discovery canonicalizes existing homes through realPath, so lexical aliases share one inspection.
|
|
29
|
+
const inspected = new Map(found.homes.map(selected => [selected.path, selected]));
|
|
30
|
+
for (const selected of defaults.homes.filter(selected => selected.exists)) {
|
|
31
|
+
const previous = inspected.get(selected.path);
|
|
32
|
+
inspected.set(selected.path, previous ? { ...previous, sources: [...new Set([...previous.sources, ...selected.sources])] } : selected);
|
|
33
|
+
}
|
|
34
|
+
const notices = [...new Set([...found.notices, ...defaults.notices])], deadline = performance.now() + 6000;
|
|
26
35
|
const homes = [];
|
|
27
|
-
for (const selected of
|
|
36
|
+
for (const selected of inspected.values()) {
|
|
28
37
|
const remaining = Math.floor(deadline - performance.now());
|
|
29
38
|
if (remaining < 1) homes.push({ home: selected, plugin: { state: "unknown", reason: "The machine profile inventory time bound was reached." }, hook: checkClaudeProfileHook(selected) });
|
|
30
39
|
else homes.push(await checkClaudeHome(selected, packageRoot, env, probe, Math.min(remaining, 6000)));
|
|
31
40
|
}
|
|
32
|
-
return { homes, notices
|
|
41
|
+
return { homes, notices, complete: found.complete && defaults.complete && performance.now() <= deadline };
|
|
33
42
|
}
|
package/src/cli/claude-homes.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { isAbsolute, join } from "node:path";
|
|
|
3
3
|
import { realPath } from "../hub/project.ts";
|
|
4
4
|
import { launcherOf, recordPath, MAX_COMMAND_MS, type ProcessIdentity } from "./terminal-recovery.ts";
|
|
5
5
|
import { CLAUDE_PLUGIN_INVENTORY, PLUGIN, nextStep, pluginState, type InstalledPlugin, type Marketplace, type PluginState, type SetupStep } from "./setup.ts";
|
|
6
|
-
import { checkClaudeProfileHook, type ClaudeProfileHookCheck } from "./claude-profile-hook.ts";
|
|
6
|
+
import { checkClaudeProfileHook, type ClaudeProfileHookCheck, type ClaudeProfileHookOptions } from "./claude-profile-hook.ts";
|
|
7
7
|
|
|
8
8
|
export type ClaudeHomeSource = "environment" | "default" | "recorded-launch";
|
|
9
9
|
export interface ClaudeHome { path: string; exists: boolean; sources: ClaudeHomeSource[] }
|
|
@@ -93,12 +93,12 @@ export interface ClaudeHomeCheck {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** Read-only per-home plugin inspection. Installation never proves that a live permission hook is present. */
|
|
96
|
-
export async function checkClaudeHome(home: ClaudeHome, packageRoot: string, env: NodeJS.ProcessEnv, probe: ClaudeHomeProbe, timeoutMs = MAX_COMMAND_MS, probeMissingDefault = false): Promise<ClaudeHomeCheck> {
|
|
96
|
+
export async function checkClaudeHome(home: ClaudeHome, packageRoot: string, env: NodeJS.ProcessEnv, probe: ClaudeHomeProbe, timeoutMs = MAX_COMMAND_MS, probeMissingDefault = false, hookOptions?: ClaudeProfileHookOptions): Promise<ClaudeHomeCheck> {
|
|
97
97
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_COMMAND_MS) throw new Error("invalid Claude plugin probe time bound");
|
|
98
|
-
const hook = checkClaudeProfileHook(home);
|
|
98
|
+
const hook = checkClaudeProfileHook(home, hookOptions);
|
|
99
99
|
const unknown = (): ClaudeHomeCheck => ({ home, plugin: { state: "unknown", reason: "Claude plugin inventory is unavailable or invalid for this home." }, hook });
|
|
100
100
|
if (hook.state === "refused") return unknown();
|
|
101
|
-
if (!home.exists && !(probeMissingDefault && home.sources.includes("default"))) return { home, plugin: { state: "missing" }, next: nextStep([], [], packageRoot), hook };
|
|
101
|
+
if (!home.exists && !(probeMissingDefault && home.sources.includes("default"))) return { home, plugin: { state: "missing" }, next: nextStep([], [], packageRoot, validPath(env.HOME) ? env.HOME : undefined), hook };
|
|
102
102
|
const scoped = claudeHomeEnvironment(home, env), deadline = performance.now() + timeoutMs;
|
|
103
103
|
const read = async (argv: readonly string[]): Promise<unknown[] | undefined> => {
|
|
104
104
|
const remaining = Math.floor(deadline - performance.now());
|
|
@@ -118,6 +118,6 @@ export async function checkClaudeHome(home: ClaudeHome, packageRoot: string, env
|
|
|
118
118
|
(row.version === undefined || typeof row.version === "string" && row.version.length <= 64 && (row.id !== PLUGIN ? !/[\u0000-\u001f\u007f-\u009f]/.test(row.version) : /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$/.test(row.version))) &&
|
|
119
119
|
(row.installPath === undefined || validPath(row.installPath))) ||
|
|
120
120
|
!marketplaces.every(row => object(row) && typeof row.name === "string" && row.name.length <= 256 && (row.path === undefined || validPath(row.path)))) return unknown();
|
|
121
|
-
return { home, plugin: pluginState(plugins as InstalledPlugin[], packageRoot), next: nextStep(plugins as InstalledPlugin[], marketplaces as Marketplace[], packageRoot), hook };
|
|
121
|
+
return { home, plugin: pluginState(plugins as InstalledPlugin[], packageRoot), next: nextStep(plugins as InstalledPlugin[], marketplaces as Marketplace[], packageRoot, validPath(env.HOME) ? env.HOME : undefined), hook };
|
|
122
122
|
} catch { return unknown(); }
|
|
123
123
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, rmSync, writeFileSync, type Stats } from "node:fs";
|
|
1
|
+
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, rmSync, statSync, writeFileSync, type Stats } from "node:fs";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
-
import { canonicalJson } from "../hub/packs.ts";
|
|
5
4
|
import { factsHook } from "./facts-hook.ts";
|
|
6
5
|
import { shellQuote } from "./shell-quote.ts";
|
|
6
|
+
import { stableBunRuntime, stablePackageRoot } from "./install-paths.ts";
|
|
7
7
|
import type { ClaudeHome } from "./claude-homes.ts";
|
|
8
8
|
|
|
9
9
|
export interface ClaudeProfileHookOptions { script?: string; runtime?: string }
|
|
@@ -25,11 +25,29 @@ interface HookPlan {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function hookEntry(options: ClaudeProfileHookOptions): Record<string, unknown> {
|
|
28
|
-
const script = options.script ?? join(import.meta.dir, "claude-profile-hook.ts"), runtime = options.runtime ??
|
|
28
|
+
const script = options.script ?? join(stablePackageRoot(join(import.meta.dir, "../..")), "src/cli/claude-profile-hook.ts"), runtime = options.runtime ?? stableBunRuntime();
|
|
29
29
|
if (!validPath(script) || !validPath(runtime)) throw new Error("invalid profile hook command");
|
|
30
30
|
return { matcher: "*", hooks: [{ type: "command", command: `${shellQuote(runtime)} ${shellQuote(script)} --run`, timeout: 5 }] };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
interface ManagedProfileHook { entry: Record<string, unknown>; hook: Record<string, unknown>; runtime: string; script: string }
|
|
34
|
+
/** Own only the generated command grammar; matcher, timeout and caller metadata remain theirs. */
|
|
35
|
+
function managedProfileHook(candidate: unknown): ManagedProfileHook | undefined {
|
|
36
|
+
if (!object(candidate) || !Array.isArray(candidate.hooks) || candidate.hooks.length !== 1) return;
|
|
37
|
+
const hook = candidate.hooks[0];
|
|
38
|
+
if (!object(hook) || hook.type !== "command" || typeof hook.command !== "string") return;
|
|
39
|
+
const match = /^'((?:[^']|'\\'')*)' '((?:[^']|'\\'')*)' --run$/.exec(hook.command);
|
|
40
|
+
if (!match) return;
|
|
41
|
+
const runtime = match[1]!.replace(/'\\''/g, "'"), script = match[2]!.replace(/'\\''/g, "'");
|
|
42
|
+
if (!validPath(runtime) || !validPath(script) || !script.endsWith("/src/cli/claude-profile-hook.ts")
|
|
43
|
+
|| hook.command !== `${shellQuote(runtime)} ${shellQuote(script)} --run`) return;
|
|
44
|
+
return { entry: candidate, hook, runtime, script };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hookPathPresent(file: string): boolean {
|
|
48
|
+
try { return statSync(file).isFile(); } catch { return false; }
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
function settingsSource(file: string): { value: Record<string, unknown>; source?: HookPlan["source"] } {
|
|
34
52
|
let fd: number | undefined;
|
|
35
53
|
try {
|
|
@@ -67,13 +85,24 @@ function planClaudeProfileHook(home: ClaudeHome, options: ClaudeProfileHookOptio
|
|
|
67
85
|
if (value.hooks !== undefined && !object(value.hooks)) return refuse("Claude's hook configuration is malformed.");
|
|
68
86
|
const hooks = value.hooks ?? {}, existing = hooks.PreToolUse ?? [];
|
|
69
87
|
if (!Array.isArray(existing)) return refuse("Claude's PreToolUse hook list is malformed.");
|
|
70
|
-
const entry = hookEntry(options),
|
|
71
|
-
const
|
|
72
|
-
const
|
|
88
|
+
const entry = hookEntry(options), expectedCommand = (entry.hooks as { command: string }[])[0]!.command;
|
|
89
|
+
const managed = existing.map(managedProfileHook), first = managed.find(candidate => candidate !== undefined);
|
|
90
|
+
const count = managed.filter(candidate => candidate !== undefined).length;
|
|
91
|
+
const stale = managed.some(candidate => candidate && (!hookPathPresent(candidate.runtime) || !hookPathPresent(candidate.script)));
|
|
92
|
+
const configured = count === 1 && first?.hook.command === expectedCommand && !stale;
|
|
93
|
+
const normalized = existing.flatMap((candidate, index) => {
|
|
94
|
+
const owned = managed[index];
|
|
95
|
+
if (!owned) return [candidate];
|
|
96
|
+
return owned === first ? [{ ...owned.entry, hooks: [{ ...owned.hook, command: expectedCommand }] }] : [];
|
|
97
|
+
});
|
|
98
|
+
if (!first) normalized.push(entry);
|
|
99
|
+
const next = configured ? undefined : `${JSON.stringify({ ...value, hooks: { ...hooks, PreToolUse: normalized } }, null, 2)}\n`;
|
|
73
100
|
if (next !== undefined && Buffer.byteLength(next, "utf8") > MAX_SETTINGS_BYTES) return refuse("Claude's settings would exceed the bounded profile settings size.");
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
let reason = "The project-neutral profile hook is not configured for this install; run ahub setup.";
|
|
102
|
+
if (count > 1) reason = "Duplicate managed profile hooks need normalization; run ahub setup.";
|
|
103
|
+
if (stale) reason = "A managed profile hook script or runtime is missing; run ahub setup to refresh the selected home's hook.";
|
|
104
|
+
if (configured) reason = "The project-neutral profile hook is configured; this does not prove live permission enforcement.";
|
|
105
|
+
return { file, home: directory, parent, source, next, check: { state: configured ? "installed" : "missing", reason } };
|
|
77
106
|
} catch { return refuse("Claude profile settings could not be safely inspected; no settings were changed."); }
|
|
78
107
|
}
|
|
79
108
|
|