mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: unify MCP bridge setup and system prompt delivery
Extract duplicate MCP bridge initialization code from codexLocalLauncher and codexRemoteLauncher into a new buildHapiMcpBridge utility. This ensures both modes use identical server setup logic and provides a single source of truth for MCP configuration. Update codexStartConfig to deliver system prompt via developer_instructions instead of appending to the user message. This ensures consistent behavior between local and remote modes and properly separates system guidance from user intent.
This commit is contained in:
@@ -5,8 +5,7 @@ import { Future } from '@/utils/future';
|
||||
import { createCodexSessionScanner } from './utils/codexSessionScanner';
|
||||
import { convertCodexEvent } from './utils/codexEventConverter';
|
||||
import { getLocalLaunchExitReason } from '@/agent/localLaunchPolicy';
|
||||
import { startHappyServer } from '@/claude/utils/startHappyServer';
|
||||
import { getHappyCliCommand } from '@/utils/spawnHappyCLI';
|
||||
import { buildHapiMcpBridge } from './utils/buildHapiMcpBridge';
|
||||
|
||||
export async function codexLocalLauncher(session: CodexSession): Promise<'switch' | 'exit'> {
|
||||
let exitReason: 'switch' | 'exit' | null = null;
|
||||
@@ -14,14 +13,7 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
|
||||
const exitFuture = new Future<void>();
|
||||
|
||||
// Start hapi server for MCP bridge (same as remote mode)
|
||||
const happyServer = await startHappyServer(session.client);
|
||||
const bridgeCommand = getHappyCliCommand(['mcp', '--url', happyServer.url]);
|
||||
const mcpServers = {
|
||||
hapi: {
|
||||
command: bridgeCommand.command,
|
||||
args: bridgeCommand.args
|
||||
}
|
||||
};
|
||||
const { server: happyServer, mcpServers } = await buildHapiMcpBridge(session.client);
|
||||
logger.debug(`[codex-local]: Started hapi MCP bridge server at ${happyServer.url}`);
|
||||
|
||||
const handleSessionMatchFailed = (message: string) => {
|
||||
|
||||
@@ -11,8 +11,7 @@ import { DiffProcessor } from './utils/diffProcessor';
|
||||
import { logger } from '@/ui/logger';
|
||||
import { CodexDisplay } from '@/ui/ink/CodexDisplay';
|
||||
import type { CodexSessionConfig } from './types';
|
||||
import { getHappyCliCommand } from '@/utils/spawnHappyCLI';
|
||||
import { startHappyServer } from '@/claude/utils/startHappyServer';
|
||||
import { buildHapiMcpBridge } from './utils/buildHapiMcpBridge';
|
||||
import { emitReadyIfIdle } from './utils/emitReadyIfIdle';
|
||||
import type { CodexSession } from './session';
|
||||
import type { EnhancedMode } from './loop';
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
type RemoteLauncherExitReason
|
||||
} from '@/modules/common/remote/RemoteLauncherBase';
|
||||
|
||||
type HappyServer = Awaited<ReturnType<typeof startHappyServer>>;
|
||||
type HappyServer = Awaited<ReturnType<typeof buildHapiMcpBridge>>['server'];
|
||||
|
||||
class CodexRemoteLauncher extends RemoteLauncherBase {
|
||||
private readonly session: CodexSession;
|
||||
@@ -420,15 +419,8 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
|
||||
}
|
||||
});
|
||||
|
||||
const happyServer = await startHappyServer(session.client);
|
||||
const { server: happyServer, mcpServers } = await buildHapiMcpBridge(session.client);
|
||||
this.happyServer = happyServer;
|
||||
const bridgeCommand = getHappyCliCommand(['mcp', '--url', happyServer.url]);
|
||||
const mcpServers = {
|
||||
hapi: {
|
||||
command: bridgeCommand.command,
|
||||
args: bridgeCommand.args
|
||||
}
|
||||
} as const;
|
||||
|
||||
this.setupAbortHandlers(session.client.rpcHandlerManager, {
|
||||
onAbort: () => this.handleAbort(),
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Unified MCP bridge setup for Codex local and remote modes.
|
||||
*
|
||||
* This module provides a single source of truth for starting the hapi MCP
|
||||
* bridge server and generating the MCP server configuration that Codex needs.
|
||||
*/
|
||||
|
||||
import { startHappyServer } from '@/claude/utils/startHappyServer';
|
||||
import { getHappyCliCommand } from '@/utils/spawnHappyCLI';
|
||||
import type { ApiSessionClient } from '@/api/apiSession';
|
||||
|
||||
/**
|
||||
* MCP server entry configuration.
|
||||
*/
|
||||
export interface McpServerEntry {
|
||||
command: string;
|
||||
args: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of MCP server names to their configurations.
|
||||
*/
|
||||
export type McpServersConfig = Record<string, McpServerEntry>;
|
||||
|
||||
/**
|
||||
* Result of starting the hapi MCP bridge.
|
||||
*/
|
||||
export interface HapiMcpBridge {
|
||||
/** The running server instance */
|
||||
server: {
|
||||
url: string;
|
||||
stop: () => void;
|
||||
};
|
||||
/** MCP server config to pass to Codex (works for both CLI and SDK) */
|
||||
mcpServers: McpServersConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the hapi MCP bridge server and return the configuration
|
||||
* needed to connect Codex to it.
|
||||
*
|
||||
* This is the single source of truth for MCP bridge setup,
|
||||
* used by both local and remote launchers.
|
||||
*/
|
||||
export async function buildHapiMcpBridge(client: ApiSessionClient): Promise<HapiMcpBridge> {
|
||||
const happyServer = await startHappyServer(client);
|
||||
const bridgeCommand = getHappyCliCommand(['mcp', '--url', happyServer.url]);
|
||||
|
||||
return {
|
||||
server: {
|
||||
url: happyServer.url,
|
||||
stop: happyServer.stop
|
||||
},
|
||||
mcpServers: {
|
||||
hapi: {
|
||||
command: bridgeCommand.command,
|
||||
args: bridgeCommand.args
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildCodexStartConfig } from './codexStartConfig';
|
||||
import { codexSystemPrompt } from './systemPrompt';
|
||||
|
||||
describe('buildCodexStartConfig', () => {
|
||||
const mcpServers = { hapi: { command: 'node', args: ['mcp'] } };
|
||||
@@ -15,7 +16,10 @@ describe('buildCodexStartConfig', () => {
|
||||
|
||||
expect(config.sandbox).toBe('danger-full-access');
|
||||
expect(config['approval-policy']).toBe('never');
|
||||
expect(config.config).toEqual({ mcp_servers: mcpServers });
|
||||
expect(config.config).toEqual({
|
||||
mcp_servers: mcpServers,
|
||||
developer_instructions: codexSystemPrompt
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores CLI overrides when permission mode is not default', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CodexSessionConfig } from '../types';
|
||||
import type { EnhancedMode } from '../loop';
|
||||
import type { CodexCliOverrides } from './codexCliOverrides';
|
||||
import { TITLE_INSTRUCTION } from './systemPrompt';
|
||||
import { codexSystemPrompt } from './systemPrompt';
|
||||
|
||||
function resolveApprovalPolicy(mode: EnhancedMode): CodexSessionConfig['approval-policy'] {
|
||||
switch (mode.permissionMode) {
|
||||
@@ -42,11 +42,14 @@ export function buildCodexStartConfig(args: {
|
||||
const resolvedApprovalPolicy = cliOverrides?.approvalPolicy ?? approvalPolicy;
|
||||
const resolvedSandbox = cliOverrides?.sandbox ?? sandbox;
|
||||
|
||||
const prompt = args.first ? `${args.message}\n\n${TITLE_INSTRUCTION}` : args.message;
|
||||
const config: Record<string, unknown> = { mcp_servers: args.mcpServers };
|
||||
if (args.developerInstructions) {
|
||||
config.developer_instructions = args.developerInstructions;
|
||||
}
|
||||
const prompt = args.message;
|
||||
const baseInstructions = codexSystemPrompt;
|
||||
const config: Record<string, unknown> = {
|
||||
mcp_servers: args.mcpServers,
|
||||
developer_instructions: args.developerInstructions
|
||||
? `${baseInstructions}\n\n${args.developerInstructions}`
|
||||
: baseInstructions
|
||||
};
|
||||
const startConfig: CodexSessionConfig = {
|
||||
prompt,
|
||||
sandbox: resolvedSandbox,
|
||||
|
||||
Reference in New Issue
Block a user