feat(cli): HAPI_DISABLE_PEER_TOOLS — hide peer-session tools from wrapped agents (#2052)

* feat(cli): add HAPI_DISABLE_PEER_TOOLS to hide peer-session tools from 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>

* test(cli): cover peer-tool prompt steer and default bridge tool list

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>

* test(cli): keep peer-tools env set through prompt assertions

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>

---------

Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
admin
2026-10-26 10:02:06 +08:00
committed by GitHub
co-authored by HAPI Claude Code
parent d3f7c107d5
commit 62872d80fa
16 changed files with 451 additions and 132 deletions
+4 -3
View File
@@ -6,7 +6,8 @@
*/
import { trimIdent } from '@/utils/trimIdent';
import { buildSessionCitationSteerInstruction } from '@hapi/protocol/sessionCitation';
import { buildSessionCitationSteerInstruction, SESSION_CITATION_DISABLED_STEER } from '@hapi/protocol/sessionCitation';
import { isPeerToolsDisabled } from '@/modules/common/peerTools';
import { HAPI_MCP_BRIDGE_PROMPT } from '@/modules/common/hapiMcpBridgePrompt';
import {
DISPLAY_IMAGE_PROMPT_HAPI_MCP,
@@ -21,7 +22,7 @@ import { withSessionSummaryInstruction } from '@/modules/common/sessionSummaryIn
*/
export const TITLE_INSTRUCTION = trimIdent(`
${HAPI_MCP_BRIDGE_PROMPT}
${buildSessionCitationSteerInstruction({
${isPeerToolsDisabled() ? SESSION_CITATION_DISABLED_STEER : buildSessionCitationSteerInstruction({
inspectTool: 'hapi_inspect_peer',
pingTool: 'hapi_ping_peer',
listPeersTool: 'hapi_list_peers',
@@ -41,7 +42,7 @@ export const OPENCODE_NATIVE_TOOL_INSTRUCTION = trimIdent(`
${DISPLAY_IMAGE_PROMPT_HAPI_MCP}
${DISPLAY_VIDEO_PROMPT_HAPI_MCP}
${DISPLAY_MEDIA_PROMPT_HAPI_MCP}
${buildSessionCitationSteerInstruction({
${isPeerToolsDisabled() ? SESSION_CITATION_DISABLED_STEER : buildSessionCitationSteerInstruction({
inspectTool: 'hapi_inspect_peer',
pingTool: 'hapi_ping_peer',
listPeersTool: 'hapi_list_peers',