Repository navigation
Conversation
…m agents Wrapped agents with peer tools can browse the session list and message other sessions, polluting peer contexts on busy runners. Setting HAPI_DISABLE_PEER_TOOLS=1 (true/yes also accepted) in the runner environment now removes the peer surface entirely instead of advertising it and refusing: - the session MCP server does not register list_peers / inspect_peer / ping_peer, and they are dropped from the toolNames consumed by Claude --allowedTools and the codex/generic bridge --tools lists - the stdio bridge filters peer names even when a stale --tools value lists them explicitly - flavor system prompts (claude/codex/opencode) swap the peer steer block for a short notice that keeps the hub-path-is-not-a-file guard while pointing citations at the user - the peer core (pingPeer/inspectPeer/listPeerSessions) refuses with a new 'disabled' error code before any network call, so shell `hapi ping-peer` / `inspect-peer` from inside a wrapped agent fails loudly as defense in depth Operators scope the variable to the runner (systemd/launchd/guard env); an interactive terminal without it keeps full CLI access. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
Adds regression tests for the two surfaces the kill switch mutates beyond the explicit --tools path: - happyMcpStdioBridge: default tool list (no --tools) keeps the peer tools as a positive control, then HAPI_DISABLE_PEER_TOOLS=1 filters them out of the same default seed. - New cross-flavor prompt test: the citation steer baked into the claude/codex/opencode prompt consts swaps to the disabled notice under the env flag and keeps the flavor-specific "Call \"<tool>\"" instruction by default (module consts evaluate env at import, so each mode is imported fresh via vi.resetModules + dynamic import). via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
Keeps HAPI_DISABLE_PEER_TOOLS applied until after the assertions and restores it at test end. Some upstream shapes resolve the citation steer at call time (getSystemPrompt(env)) rather than baking it into the import-time const, so restoring the env inside the import helper made those shapes observe the enabled steer and fail. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
…essions The peer-tools kill switch shipped runner-env-only, which operators cannot reach from the web UI. Add a hub-persisted global toggle with the same delivery path as sessionSummaryContract: - Settings gains disablePeerTools (default off = tools available) - GET/PUT /api/hub-settings expose it (write stays owner-only; the "at least one field" refine covers the new key) - POST /cli/sessions and GET /cli/sessions/:id responses carry the resolved value so CLI sessions apply it at bootstrap The value is advisory on the wire: older runners strip the unknown field, and HAPI_DISABLE_PEER_TOOLS on a runner still overrides it client-side. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
The CLI gate now resolves like sessionSummaryContract: an explicit HAPI_DISABLE_PEER_TOOLS value wins in both directions (0/false/off/no keep the tools, anything else — including typos — disables them, so the kill switch fails safe), otherwise the hub preference applied at session bootstrap decides, otherwise tools stay available. - peerTools gains applyHubPeerToolsDisabled module state - /cli/sessions create + get responses feed it into the gate before any flavor launcher or MCP server build reads the toggle - the core refusal message now names both sources (hub administrator or env) since either can be the active one The stdio bridge keeps env-only semantics: it runs in its own child process that never bootstraps a session, and its --tools list already comes from the (pruned) session toolNames. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
Convert the three flavor prompts (claude BASE_SYSTEM_PROMPT IIFE const, codex/opencode frozen TITLE_INSTRUCTION / OPENCODE_NATIVE_TOOL_INSTRUCTION consts) into env-parameterized builders called from the existing getters. The hub disablePeerTools preference arrives during session bootstrap, after module import, so the import-time snapshots could never reflect it. Deprecated const exports stay as import-time process.env snapshots for the existing byte-identity tests; production call sites are all no-arg getter calls and pick up the new resolution automatically. Extend systemPrompt.peerTools.test.ts with hub-preference cases through the getters (env unset + hub disabled -> disabled notice in all flavors; explicit env overrides hub in both directions). via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
Owner-gated section mirroring the session summary switches: reads disablePeerTools from GET /api/hub-settings and PATCHes it alone via updateHubSettings. Locale strings for en / zh-CN / ru; the description documents new-session-only semantics, the terminal ping-peer exemption, and the per-runner env override. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
Document Settings -> General -> Peer session tools as the primary switch (hub-persisted, new sessions on every runner) and demote HAPI_DISABLE_PEER_TOOLS to a per-runner hard override, including the resolution order (non-empty env decides and fails safe on typos; else hub value; else tools available) and the terminal ping-peer exemption. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Review mode: initial
Requirement — Pass
A hub-owner setting to hide peer tools from new agent sessions addresses unwanted cross-session browsing and messaging without configuring every runner.
Evidence
- The snapshot description for PR #2056 establishes the use case, environment-override precedence, and new-session-only behavior.
- Base cli/src/claude/utils/startHappyServer.ts registers all three peer tools unconditionally; the existing capability does not provide the requested hub-wide control.
Approach — Pass
Persisting an owner-controlled setting, delivering it during session bootstrap, and applying it to MCP registration and prompts is a viable direction. The defects below concern propagation and lifecycle handling.
Evidence
- hub/src/web/routes/hubSettings.ts:32 retains owner-only mutations and updates settings through the existing serialized writer.
- hub/src/web/routes/cli.ts:147 delivers the preference during bootstrap; cli/src/api/api.ts:95 consumes it.
- cli/src/claude/utils/startHappyServer.ts:305 conditionally registers peer tools.
Code — Reviewed
Reviewed the entire fixed-head merge-base diff, including the stacked changes. Found three defects affecting shell enforcement, shared Codex sessions, and prompt initialization.
- [Major] Propagate the hub preference into agent shell processes —
cli/src/modules/pingPeer/pingPeer.ts:46 - [Minor] Keep the resolved preference scoped to each shared Codex root —
cli/src/modules/common/peerTools.ts:26 - [Minor] Resolve the initial Codex prompt after hub bootstrap —
cli/src/codex/utils/systemPrompt.ts:52
Testing
Not run (automation; PR code execution is prohibited).
- No PR code, tests, builds, or scripts were executed, as required by the read-only review boundary.
- Fixed-head CI: drift-gate, integration, and windows-codex-mcp succeeded; test remained in progress at the last check.
- Added tests exercise settings, schemas, and in-process gates, but do not cover shell-child inheritance, mixed-preference shared roots, or initial Codex prompt timing.
HAPI Bot
| * wrapped agent) lands here and fails loudly. | ||
| */ | ||
| function assertPeerToolsEnabled(): void { | ||
| if (isPeerToolsDisabled()) { |
There was a problem hiding this comment.
[Major] Propagate the hub preference into agent shell processes
With the hub toggle enabled and HAPI_DISABLE_PEER_TOOLS unset, the preference exists only in the wrapper's module memory. An agent running hapi ping-peer, hapi ping-peer --list, or hapi inspect-peer starts a fresh process whose hubPreference is undefined. Those commands never bootstrap a session, so this guard allows them to browse or message peers using existing credentials. This contradicts the documented refusal inside wrapped-agent shells.
Suggested fix
Pass the resolved hub preference into wrapped-agent child environments, including per-root shell settings for shared Codex. Preserve explicit environment overrides and the operator-terminal exemption. Add coverage across the child-process boundary.
There was a problem hiding this comment.
Fixed in 966eca8. The create-time preference now rides the session row (SessionBootstrapInfo.peerToolsDisabled) into ApiSessionClient, and every wrapped-agent child receives it as an explicit env value so a fresh hapi ping-peer resolves without module memory: claude local spawn + remote SDK (new env option on the vendored query), codex local TUI, shared Codex roots (per-root shell_environment_policy.set.HAPI_DISABLE_PEER_TOOLS), the codex app-server child and its thread params, opencode backends, and the dsh/kimi/grok/copilot/cursor ACP backends. A non-empty machine-level HAPI_DISABLE_PEER_TOOLS stays authoritative (peerToolsEnvOverlay yields no override and the deploy-time env inherits into children), and the operator terminal never passes through a wrapped-agent spawn, keeping its env-only exemption. Enabled sessions pin 0 so a later global flip cannot rewrite their children. Boundary coverage: spawn-env tests in claudeLocal.test.ts / codexLocal.test.ts, the overlay matrix in peerTools.test.ts, and the cross-flip handler regression in startHappyServer.test.ts.
|
|
||
| /** Apply the hub-resolved toggle from session create/get bootstrap. */ | ||
| export function applyHubPeerToolsDisabled(disabled: boolean): void { | ||
| hubPreference = disabled |
There was a problem hiding this comment.
[Minor] Keep the resolved preference scoped to each shared Codex root
Shared Codex hosts multiple roots in one process. Start root A with tools enabled, enable the hub toggle, then create root B through /new or fork. B's bootstrap overwrites this global preference, and A's already-registered peer tools subsequently fail the dynamic core guard with disabled. Existing sessions therefore change behavior despite the promised new-session-only semantics. MCP reconnections also resolve registration against the latest global value.
Suggested fix
Capture the effective preference per session/root and use that snapshot consistently for tool registration, tool handlers, and prompts. Add a shared-runtime regression covering roots created on opposite sides of a toggle change.
There was a problem hiding this comment.
Fixed in 966eca8. Each session captures its effective preference at create time (ApiSessionClient.peerToolsDisabled, adopted on lazy materialization) and every consumer reads that snapshot: tool registration (startHappyServer, buildHapiMcpBridge), MCP handlers (the ping-peer core guard receives the snapshot, so tools registered by an enabled session keep working after another root flips the global), prompts, and the per-root shell env. Shared roots take the snapshot from their bootstrap session inside SharedCodexRoot, so config() consistently serves registration, handlers, and prompts for that root. Added the requested shared-runtime regression: root.test.ts builds roots on opposite sides of a toggle flip and asserts each root keeps its own prompt steer and pinned shell_environment_policy value.
| */ | ||
| export function getCodexSystemPrompt(env: NodeJS.ProcessEnv = process.env): string { | ||
| return withSessionSummaryInstruction(TITLE_INSTRUCTION, env) | ||
| return withSessionSummaryInstruction(buildCodexTitleInstruction(env), env) |
There was a problem hiding this comment.
[Minor] Resolve the initial Codex prompt after hub bootstrap
Making this getter dynamic does not fix the initial Codex launch timing. shared/runtime.ts:45 calls sharedLaunchConfig before bootstrap; shared/launch.ts:150 immediately stores this getter's result in threadParams.developerInstructions. After bootstrap applies the hub preference, root.config preserves that supplied string. Consequently, a fresh Codex session with the hub toggle enabled still receives instructions advertising the disabled peer tools instead of the disabled notice.
Suggested fix
Generate HAPI's Codex developer instructions after the root has bootstrapped, instead of carrying the pre-bootstrap string from sharedLaunchConfig. Add startup-path coverage with the hub preference enabled and no environment override.
There was a problem hiding this comment.
Fixed in 966eca8. sharedLaunchConfig no longer puts developerInstructions into threadParams; each shared root resolves its prompt at request time in SharedCodexRoot.config() (params.developerInstructions ?? getCodexSystemPrompt(withPeerToolsPreference(snapshot))), which runs after that root's own bootstrap — this also covers the resume/fork paths that reused the launch-time params. Startup-path coverage added: launch.test.ts asserts threadParams carries no frozen developerInstructions, and root.test.ts covers the hub-enabled startup with no environment override, resolving the post-bootstrap prompt that advertises the peer tools.
…aries Review follow-up for the hub-wide peer-tools toggle (tiann#2056): - Create-time snapshot: POST /cli/sessions responses carry disablePeerTools on the session row (SessionBootstrapInfo) and ApiSessionClient captures it, so registration, prompts, handlers, and child envs use this session's preference instead of the process-wide module value flipped by later bootstraps. - Child-process boundary: wrapped agents shelling hapi ping-peer spawn fresh processes with no module memory, so the resolved preference travels as an explicit env value. claude local/remote, codex local/shared/app-server (per-root shell_environment_policy), opencode, dsh, kimi, grok, copilot, and cursor all pin their session's value; a non-empty machine-level HAPI_DISABLE_PEER_TOOLS stays authoritative and the operator terminal keeps its env-only exemption. - Shared Codex prompts: sharedLaunchConfig no longer freezes the pre-bootstrap developer instructions into threadParams; each root resolves them post-bootstrap in config(), and thread params builders accept an env override used by the remote launcher. - MCP handlers pass their captured snapshot into the ping-peer core guard, keeping tools registered by an enabled session working after a later session flips the global switch. - Coverage: overlay matrix, cross-flip regressions for the MCP server and shared roots on opposite sides of a toggle change, spawn-env boundary tests for claude/codex children, and snapshot propagation through ApiSessionClient incl. lazy materialization. Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
…aries Review follow-up for the hub-wide peer-tools toggle (tiann#2056): - Create-time snapshot: POST /cli/sessions responses carry disablePeerTools on the session row (SessionBootstrapInfo) and ApiSessionClient captures it, so registration, prompts, handlers, and child envs use this session's preference instead of the process-wide module value flipped by later bootstraps. - Child-process boundary: wrapped agents shelling hapi ping-peer spawn fresh processes with no module memory, so the resolved preference travels as an explicit env value. claude local/remote, codex local/shared/app-server (per-root shell_environment_policy), opencode, dsh, kimi, grok, copilot, and cursor all pin their session's value; a non-empty machine-level HAPI_DISABLE_PEER_TOOLS stays authoritative and the operator terminal keeps its env-only exemption. - Shared Codex prompts: sharedLaunchConfig no longer freezes the pre-bootstrap developer instructions into threadParams; each root resolves them post-bootstrap in config(), and thread params builders accept an env override used by the remote launcher. - MCP handlers pass their captured snapshot into the ping-peer core guard, keeping tools registered by an enabled session working after a later session flips the global switch. - Coverage: overlay matrix, cross-flip regressions for the MCP server and shared roots on opposite sides of a toggle change, spawn-env boundary tests for claude/codex children, and snapshot propagation through ApiSessionClient incl. lazy materialization. Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
There was a problem hiding this comment.
Review mode: follow-up after new commits
Requirement — Pass
A hub-owner toggle addresses unwanted cross-session browsing and messaging without requiring configuration on every runner.
Evidence
- The snapshot description establishes hub-wide control, environment-override precedence, and new-session-only behavior.
- Base cli/src/claude/utils/startHappyServer.ts:301 registers peer tools unconditionally.
Approach — Pass
Persisting an owner-controlled preference, delivering it during bootstrap, and applying session snapshots to tools, prompts, and child environments is viable. The remaining problems are implementation defects.
Evidence
- hub/src/web/routes/hubSettings.ts:32 preserves owner-only mutations through the existing serialized settings writer.
- hub/src/web/routes/cli.ts:147 returns the preference during bootstrap.
- cli/src/codex/shared/root.ts:193 resolves the initial prompt after bootstrap.
Code — Reviewed
Reviewed the entire fixed-head merge-base diff, including stacked changes. The initial Codex prompt timing is fixed; three enforcement and lifecycle defects remain.
- [Major] Apply the environment override before capturing tool availability —
cli/src/claude/utils/startHappyServer.ts:130 - [Minor] Capture the preference on session fetches too —
cli/src/api/api.ts:169 - [Minor] Propagate the preference to the remaining agent children —
cli/src/modules/pingPeer/pingPeer.ts:52
Testing
Not run (automation; PR code execution is prohibited).
- Fixed-head CI checks test, integration, windows-codex-mcp, and drift-gate all succeeded.
- No PR code, tests, builds, or scripts were executed locally, as required.
- Added coverage misses conflicting environment overrides with real bootstrap snapshots, fetched-session snapshots across shared-root changes, and the remaining launcher inheritance paths.
HAPI Bot
| // other sessions. Prefer this session's create-time snapshot over the | ||
| // module-wide preference: one runner process hosts many sessions, and a | ||
| // later bootstrap must not flip an already-running session (new-session-only). | ||
| const peerToolsDisabled = client.peerToolsDisabled ?? isPeerToolsDisabled(); |
There was a problem hiding this comment.
[Major] Apply the environment override before capturing tool availability
ApiClient stores the raw hub boolean as peerToolsDisabled, so this expression skips environment resolution whenever a current hub supplies the field. With HAPI_DISABLE_PEER_TOOLS=1 and the hub toggle off, Claude's HTTP MCP server registers all peer tools and passes false to their core guards, which also bypass environment resolution. Agents can therefore browse and message peers despite the runner hard override. Conversely, an explicit environment value of 0 cannot restore tools when the hub disables them.
Suggested fix
Resolve environment > this session's hub preference > default into one effective session snapshot, and use it consistently for registration, handlers, bridge configuration, prompts, and child environments. Cover conflicting environment/hub values using an actual bootstrap snapshot.
There was a problem hiding this comment.
Fixed in 912c873 — good catch on the raw boolean. New resolveSessionPeerToolsDisabled(hubDisabled) (= isPeerToolsDisabled(withPeerToolsPreference(hubDisabled))) is applied at both capture points — the ctor and the lazy-materialize adoption — so the stored snapshot is already env-resolved and a session bootstrapped while the machine override stands never flips later with the module value. Both directions covered in peerTools.test.ts (resolveSessionPeerToolsDisabled describe) and apiSession.test.ts (capture-time resolution for ctor + materialization paths).
| applyHubSessionSummaryContract(parsed.data.sessionSummaryContract) | ||
| } | ||
| if (typeof parsed.data.disablePeerTools === 'boolean') { | ||
| applyHubPeerToolsDisabled(parsed.data.disablePeerTools) |
There was a problem hiding this comment.
[Minor] Capture the preference on session fetches too
getSession updates only the module global and omits peerToolsDisabled from its returned row. Consequently, bootstrapExistingSession creates an ApiSessionClient without a snapshot. Cold-resume shared Codex root A with tools enabled, enable the hub toggle, and create root B before A initializes its HTTP MCP connection: B changes the global, and A's first forwarded tool call creates an MCP server without peer tools. A's prompt and advertised bridge still expose those tools, violating new-session-only behavior.
Suggested fix
Return SessionBootstrapInfo from getSession and attach the preference as on the create path. Add coverage for a resumed shared root whose MCP connection initializes after another root bootstraps with the opposite preference.
There was a problem hiding this comment.
Fixed in 912c873 — getSession now returns SessionBootstrapInfo and spreads disablePeerTools when the hub sends it, so bootstrapExistingSession (cold resume) captures its own snapshot through api.sessionSyncClient(sessionInfo) instead of racing the process-wide module value. Covered by the capture-resolution tests in apiSession.test.ts.
| * latest bootstrap — which keeps the operator terminal env-only. | ||
| */ | ||
| function assertPeerToolsEnabled(sessionPreference?: boolean): void { | ||
| if (sessionPreference ?? isPeerToolsDisabled()) { |
There was a problem hiding this comment.
[Minor] Propagate the preference to the remaining agent children
With the hub toggle enabled and the environment variable unset, this guard still permits shell commands from local Cursor, local Grok, and Pi sessions. Their spawn paths use process.env directly (cursorLocal.ts:89, grokLocal.ts:51, and runPi.ts:255), so a fresh hapi ping-peer or inspect-peer process receives neither the session snapshot nor the wrapper's module state. These agents can still list, inspect, and message peers despite the documented wrapped-shell refusal.
Suggested fix
Apply the session-resolved child environment to these remaining launch paths, preserving explicit environment overrides and the operator-terminal exemption. Add launcher coverage that verifies the inherited preference.
There was a problem hiding this comment.
Fixed in 912c873 — all three local spawns now pin the resolved session preference into the child env via withPeerToolsPreference(opts.peerToolsDisabled ?? isPeerToolsDisabled()): cursorLocal (snapshot threaded from session.client via cursorLocalLauncher), grokLocal (same via grokLocalLauncher), and the Pi PiTransport (apiSession.peerToolsDisabled). opencode was already covered through its backend overlay. New spawn-env tests: cursorLocal.test.ts (new file), grokLocal.test.ts child-env describe, and runPi.test.ts transport-env assertions (fallback pins '0', snapshot pins '1').
Review round 2 follow-ups for the session-scoped peer-tools preference: - Capture the hub preference through the machine-level deploy override at capture time (new resolveSessionPeerToolsDisabled). Storing the raw hub boolean let a session bootstrapped while HAPI_DISABLE_PEER_TOOLS stands skip env resolution entirely; the snapshot now lands already resolved so every consumer (client.peerToolsDisabled ?? isPeerToolsDisabled()) reads a stable truth in both directions. - Return disablePeerTools from getSession so cold resumes (bootstrapExistingSession) capture their own snapshot instead of racing the process-wide module value on a later cross-flip. - Pin the session preference into the child env for local Cursor, local Grok, and Pi spawns (previously bare process.env), so agents shelling hapi ping-peer resolve the session state in the fresh process. Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
There was a problem hiding this comment.
Review mode: follow-up after new commits
Requirement — Pass
A hub-owner toggle provides useful centralized control over unwanted cross-session discovery and messaging while preserving runner overrides.
Evidence
- The snapshot description establishes hub-wide control, environment precedence, and new-session-only behavior.
- Base cli/src/claude/utils/startHappyServer.ts:301 registers peer tools unconditionally.
Approach — Pass
Persisting an owner-controlled setting, delivering it during bootstrap, and capturing the effective preference per session is viable.
Evidence
- hub/src/web/routes/hubSettings.ts:32 preserves owner-only mutations through the serialized settings writer.
- cli/src/api/apiSession.ts:315 resolves the bootstrap snapshot through the environment override.
- cli/src/codex/shared/root.ts:184 applies the root preference to prompts and shell configuration.
Code — Reviewed
Reviewed the entire fixed-head merge-base diff, including stacked changes. The previous snapshot defects are fixed; preference propagation remains incomplete for two launch paths.
- [Minor] Propagate the preference into Antigravity and legacy Cursor children —
cli/src/modules/pingPeer/pingPeer.ts:52
Testing
Not run (automation; PR code execution is prohibited).
- Fixed-head CI: integration, windows-codex-mcp, and drift-gate succeeded; test remained in progress when inspected.
- No PR code, tests, builds, or scripts were executed locally, as required.
- Added launcher coverage does not cover Antigravity or legacy Cursor preference inheritance.
HAPI Bot
| * latest bootstrap — which keeps the operator terminal env-only. | ||
| */ | ||
| function assertPeerToolsEnabled(sessionPreference?: boolean): void { | ||
| if (sessionPreference ?? isPeerToolsDisabled()) { |
There was a problem hiding this comment.
[Minor] Propagate the preference into Antigravity and legacy Cursor children
With the hub toggle enabled and HAPI_DISABLE_PEER_TOOLS unset, a new Antigravity session still launches its agent using process.env alone (agy/headless/agyHeadlessDriver.ts:81,124). Cold-resumed pre-ACP Cursor sessions do the same in cursorLegacyRemoteLauncher.ts:272. A fresh hapi ping-peer or inspect-peer process launched by either agent receives neither the session snapshot nor module state, so this guard permits peer access despite the documented wrapped-shell refusal.
Suggested fix
Apply the session-resolved child environment to both launch paths, preserving explicit environment overrides. Add coverage verifying preference inheritance for Antigravity and legacy Cursor.
There was a problem hiding this comment.
Fixed in a07e0e0 — both launch paths now pin the session-resolved preference:
- Antigravity:
buildAgySpawnEnv(peerToolsDisabled?)(now exported) applieswithPeerToolsPreferenceon top of the existing GEMINI_FORCE_FILE_STORAGE / SSH-marker hardening; the defaultspawnAgythreadsthis.session.client.peerToolsDisabledthrough it. A non-empty machine-levelHAPI_DISABLE_PEER_TOOLSstays authoritative andprocess.envis never mutated. - Legacy Cursor: the pre-ACP stream-json resume spawn in
cursorLegacyRemoteLauncher.runAgentProcesspinswithPeerToolsPreference(this.session.client.peerToolsDisabled ?? isPeerToolsDisabled()).
Coverage: buildAgySpawnEnv describe in agyHeadlessDriver.test.ts (snapshot pinning + hardening vars preserved + SSH markers stripped + machine-override wins + no process.env mutation) and two new spawn-env tests in cursorLegacyRemoteLauncher.test.ts (snapshot true pins 1; machine 1 beats explicit snapshot false).
There was a problem hiding this comment.
Follow-up in 8680f73: swept the remaining wrapped-agent spawn sites proactively — copilotLocal had the same bare process.env (its remote path was already pinned via the backend env from the earlier rounds). Now pins the session-resolved preference identically, with the same four-case env coverage in copilotLocal.test.ts. Remaining process.env spawns are model probes (*Models.ts) and operator-side CLI commands, none of which run agent tools.
Review round 3 follow-up: two more wrapped-agent spawn paths still passed a bare process.env, so a fresh `hapi ping-peer` from those agents resolved neither the session snapshot nor the module state: - agyHeadlessDriver: buildAgySpawnEnv now applies the session-resolved preference on top of the existing GEMINI/SSH hardening (machine-level override stays authoritative, process.env not mutated). - cursorLegacyRemoteLauncher: the pre-ACP stream-json resume spawn pins the session snapshot the same way. Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
Same wrapped-agent spawn category as the round-2/3 fixes, swept proactively: copilotLocal still passed a bare process.env, so a fresh `hapi ping-peer` from a local Copilot agent resolved neither the session snapshot nor the module state. Pins the session-resolved preference via withPeerToolsPreference (machine-level override stays authoritative); the remote path already pinned it through the backend env. Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
There was a problem hiding this comment.
Review mode: follow-up after new commits
Requirement — Pass
A hub-owner toggle provides useful centralized control over cross-session discovery and messaging while preserving runner overrides.
Evidence
- The snapshot description establishes centralized control, environment precedence, and new-session-only behavior.
- cli/README.md:45 documents existing peer discovery and messaging commands.
Approach — Pass
Owner-only persistence, bootstrap delivery, and captured session preferences form a viable approach.
Evidence
- hub/src/web/routes/hubSettings.ts:32 restricts mutations to the owner and uses the serialized settings writer.
- cli/src/api/apiSession.ts:315 captures the environment-resolved bootstrap preference.
- cli/src/codex/shared/root.ts:184 applies the captured preference to root prompts and shell configuration.
Code — Reviewed
Reviewed the entire fixed-head merge-base diff, including stacked changes. The previous Antigravity and legacy Cursor finding is addressed; local Kimi still lacks preference propagation.
- [Minor] Propagate the preference into local Kimi launches —
cli/src/kimi/kimiRemoteLauncher.ts:56
Testing
Not run (automation; PR code execution is prohibited).
- Fixed-head CI: integration, windows-codex-mcp, and drift-gate succeeded; test remained in progress when inspected.
- No PR code, tests, builds, or scripts were executed locally, as required.
- Added launcher tests cover Antigravity and legacy Cursor; local Kimi preference inheritance remains uncovered.
HAPI Bot
| const runtimeConfig = resolveKimiRuntimeConfig({ model: this.model }); | ||
|
|
||
| const backend = createKimiBackend(); | ||
| const backend = createKimiBackend(withPeerToolsPreference(session.client.peerToolsDisabled ?? isPeerToolsDisabled())); |
There was a problem hiding this comment.
[Minor] Propagate the preference into local Kimi launches
With the hub toggle enabled and HAPI_DISABLE_PEER_TOOLS unset, starting hapi kimi defaults to local mode. Only the remote backend receives this overlay: kimiLocalLauncher.ts:119 passes no preference, and kimiLocal.ts:28 copies process.env alone. Consequently, hapi ping-peer or inspect-peer launched by the local agent receives no hub preference and permits peer access, contrary to the documented wrapped-agent shell refusal.
Suggested fix
Pass session.client.peerToolsDisabled through kimiLocalLauncher into kimiLocal and apply withPeerToolsPreference to its child environment. Add regression coverage for local launches while preserving explicit environment overrides.
There was a problem hiding this comment.
Fixed in e62ca72 — kimiLocalLauncher threads session.client.peerToolsDisabled into kimiLocal, whose child env now applies withPeerToolsPreference (machine-level override stays authoritative). Coverage: new kimiLocal.test.ts spawn-env describe (snapshot pinning both directions, machine override wins, module fallback) plus a launcher test driving the captured launch callback to assert the snapshot is threaded through.
Review round 4 follow-up: kimiLocal built its child env from a bare process.env copy and kimiLocalLauncher passed no preference, so a fresh `hapi ping-peer` from a local Kimi agent resolved neither the session snapshot nor the module state. Threads session.client.peerToolsDisabled through the launcher and applies withPeerToolsPreference (machine-level override stays authoritative). Co-Authored-By: Claude Code <noreply@anthropic.com> via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
There was a problem hiding this comment.
Review mode: follow-up after new commits
Requirement — Pass
A hub-owner toggle provides useful centralized control over peer discovery and messaging while preserving runner overrides.
Evidence
- The snapshot description establishes centralized control, environment precedence, and new-session-only behavior.
- cli/README.md:45–46 documents the existing peer discovery, inspection, and messaging capabilities.
Approach — Pass
Owner-only persistence, bootstrap delivery, and session-specific preference capture provide a viable implementation.
Evidence
- hub/src/web/routes/hubSettings.ts:32 restricts mutations to the owner and persists through the serialized settings writer.
- cli/src/api/apiSession.ts:315 captures the environment-resolved bootstrap preference.
- cli/src/codex/shared/root.ts:184 applies the captured preference to root prompts and shell configuration.
Code — Reviewed
Reviewed the entire fixed-head merge-base diff, including stacked changes. The previous local Kimi propagation finding is addressed with regression coverage. No actionable defects identified.
No reportable code issues found.
Testing
Not run (automation; PR code execution is prohibited).
- Fixed-head CI succeeded: test, integration, windows-codex-mcp, and drift-gate.
- Added coverage includes settings persistence, environment precedence, prompt and MCP gating, session preference isolation, and child-environment propagation.
- No PR code, tests, builds, or scripts were executed locally, as required.
HAPI Bot
Stacked on #2052 — merge after it. The commits specific to this PR start at
c0d5ebf7; until #2052 merges, the diff below also shows its two commits.What
Promotes the peer-tools kill switch from a runner env var to a first-class hub-wide toggle in the web UI (Settings → General → Peer session tools → Disable peer session tools), while keeping
HAPI_DISABLE_PEER_TOOLSas a per-runner hard override.Motivation (same as #2052): remotely-driven agents browsing the session list and messaging other sessions pollutes their contexts. The env switch works but is invisible in the UI and per-machine; this makes it a hub-owner setting with one click.
How
Mirrors the existing
sessionSummaryContractsettings chain end to end:disablePeerToolsonCreateSessionResponse(optional, both create and fetch) andHubSettingsResponse(required);UpdateHubSettingsRequestaccepts it alone (at-least-one-field refine extended).Settings.disablePeerTools?persisted in settings.json;GET/PUT /api/hub-settingsexpose it (PUT owner-only, no side effects); the value ridesPOST /cli/sessionsandGET /cli/sessions/:idresponses.applyHubPeerToolsDisabled()module state applied at session bootstrap (same placesessionSummaryContractlands). Resolution order: non-empty env decides (0/false/off/no→ available, anything else → disabled) > hub value > default (tools available). The three flavor prompts (claude/codex/opencode) are converted from import-time frozen consts to call-time builders so the hub value — which arrives after module import — is honored; the deprecated const exports stay as import-time snapshots for the existing byte-identity tests.ping-peerexemption, and the env override.Behavior notes
0/false/off/noexplicitly re-enable. This matches thesessionSummaryContractenv convention.hapi ping-peernever bootstraps a session, so the hub value cannot reach it; it stays env-only. Operator terminals keep working unless they export the env globally.disabled) now mentions both sources in its message.Tests
shared 330/0 · hub 1393+3 skip · cli 2999+11 skip · web 3350/0 (each suite run solo), tsc clean on hub/cli/web. New coverage: settings round-trip + lone-key PUT, schema wire shapes, hub-preference gate matrix (env × hub both directions), all-flavor prompt steer through the getters, MCP registration with hub preference, web toggle render + mutate, pingPeer refusal with the hub administrator message.
🤖 Generated with Claude Code