Skip to content

feat: hub-wide peer-tools toggle in web Settings (stacked on #2052) - #2056

Open
miuiadmin wants to merge 14 commits into
tiann:mainfrom
miuiadmin:feat/peer-tools-web-toggle
Open

miuiadmin wants to merge 14 commits into
tiann:mainfrom
miuiadmin:feat/peer-tools-web-toggle

Conversation

@miuiadmin

Copy link
Copy Markdown
Contributor

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_TOOLS as 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 sessionSummaryContract settings chain end to end:

  • shared: disablePeerTools on CreateSessionResponse (optional, both create and fetch) and HubSettingsResponse (required); UpdateHubSettingsRequest accepts it alone (at-least-one-field refine extended).
  • hub: Settings.disablePeerTools? persisted in settings.json; GET/PUT /api/hub-settings expose it (PUT owner-only, no side effects); the value rides POST /cli/sessions and GET /cli/sessions/:id responses.
  • cli: applyHubPeerToolsDisabled() module state applied at session bootstrap (same place sessionSummaryContract lands). 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.
  • web: owner-gated section after Session status summaries, locales en / zh-CN / ru; the switch description documents new-session-only semantics, the terminal ping-peer exemption, and the env override.

Behavior notes

  • Env parsing normalized vs feat(cli): HAPI_DISABLE_PEER_TOOLS — hide peer-session tools from wrapped agents #2052: any non-empty value now decides, so typos fail safe (disabled) instead of silently re-enabling the tools; 0/false/off/no explicitly re-enable. This matches the sessionSummaryContract env convention.
  • New sessions only — running sessions keep their tool set until they end (same as feat(cli): HAPI_DISABLE_PEER_TOOLS — hide peer-session tools from wrapped agents #2052).
  • Terminal hapi ping-peer never bootstraps a session, so the hub value cannot reach it; it stays env-only. Operator terminals keep working unless they export the env globally.
  • Backward compatible both ways: new hub → old CLI (zod strips the unknown key); old hub → new CLI (optional field absent = no-op).
  • The refusal defense-in-depth (peer core rejects with 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

miuiadmin and others added 8 commits October 17, 2026 18:47
…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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread cli/src/modules/pingPeer/pingPeer.ts Outdated
* wrapped agent) lands here and fails loudly.
*/
function assertPeerToolsEnabled(): void {
if (isPeerToolsDisabled()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
miuiadmin added a commit to miuiadmin/hapi that referenced this pull request Oct 18, 2026
…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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread cli/src/api/api.ts
applyHubSessionSummaryContract(parsed.data.sessionSummaryContract)
}
if (typeof parsed.data.disablePeerTools === 'boolean') {
applyHubPeerToolsDisabled(parsed.data.disablePeerTools)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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').

@heavygee heavygee added enhancement New feature or request area:cli CLI, runner, agent wrappers area:hub Hub server (API, sync, store) area:web Web PWA / React client community-pr PR from non-collaborator contributor labels Oct 18, 2026
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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a07e0e0 — both launch paths now pin the session-resolved preference:

  • Antigravity: buildAgySpawnEnv(peerToolsDisabled?) (now exported) applies withPeerToolsPreference on top of the existing GEMINI_FORCE_FILE_STORAGE / SSH-marker hardening; the default spawnAgy threads this.session.client.peerToolsDisabled through it. A non-empty machine-level HAPI_DISABLE_PEER_TOOLS stays authoritative and process.env is never mutated.
  • Legacy Cursor: the pre-ACP stream-json resume spawn in cursorLegacyRemoteLauncher.runAgentProcess pins withPeerToolsPreference(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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

miuiadmin and others added 2 commits October 18, 2026 17:48
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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli CLI, runner, agent wrappers area:hub Hub server (API, sync, store) area:web Web PWA / React client community-pr PR from non-collaborator contributor enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants