mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
190 lines
7.4 KiB
TypeScript
190 lines
7.4 KiB
TypeScript
import { logger } from '@/ui/logger';
|
|
import { loop, type EnhancedMode, type PermissionMode } from './loop';
|
|
import { MessageQueue2 } from '@/utils/MessageQueue2';
|
|
import { hashObject } from '@/utils/deterministicJson';
|
|
import { registerKillSessionHandler } from '@/claude/registerKillSessionHandler';
|
|
import type { AgentState } from '@/api/types';
|
|
import type { CodexSession } from './session';
|
|
import { parseCodexCliOverrides } from './utils/codexCliOverrides';
|
|
import { bootstrapSession } from '@/agent/sessionFactory';
|
|
import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle';
|
|
import { isPermissionModeAllowedForFlavor } from '@hapi/protocol';
|
|
import { CodexCollaborationModeSchema, PermissionModeSchema } from '@hapi/protocol/schemas';
|
|
import { formatMessageWithAttachments } from '@/utils/attachmentFormatter';
|
|
|
|
export { emitReadyIfIdle } from './utils/emitReadyIfIdle';
|
|
|
|
export async function runCodex(opts: {
|
|
startedBy?: 'runner' | 'terminal';
|
|
codexArgs?: string[];
|
|
permissionMode?: PermissionMode;
|
|
resumeSessionId?: string;
|
|
model?: string;
|
|
}): Promise<void> {
|
|
const workingDirectory = process.cwd();
|
|
const startedBy = opts.startedBy ?? 'terminal';
|
|
|
|
logger.debug(`[codex] Starting with options: startedBy=${startedBy}`);
|
|
|
|
let state: AgentState = {
|
|
controlledByUser: false
|
|
};
|
|
const { api, session } = await bootstrapSession({
|
|
flavor: 'codex',
|
|
startedBy,
|
|
workingDirectory,
|
|
agentState: state,
|
|
model: opts.model
|
|
});
|
|
|
|
const startingMode: 'local' | 'remote' = startedBy === 'runner' ? 'remote' : 'local';
|
|
|
|
setControlledByUser(session, startingMode);
|
|
|
|
const messageQueue = new MessageQueue2<EnhancedMode>((mode) => hashObject({
|
|
permissionMode: mode.permissionMode,
|
|
model: mode.model,
|
|
collaborationMode: mode.collaborationMode
|
|
}));
|
|
|
|
const codexCliOverrides = parseCodexCliOverrides(opts.codexArgs);
|
|
const sessionWrapperRef: { current: CodexSession | null } = { current: null };
|
|
|
|
let currentPermissionMode: PermissionMode = opts.permissionMode ?? 'default';
|
|
let currentModel = opts.model;
|
|
let currentCollaborationMode: EnhancedMode['collaborationMode'] = 'default';
|
|
|
|
const lifecycle = createRunnerLifecycle({
|
|
session,
|
|
logTag: 'codex',
|
|
stopKeepAlive: () => sessionWrapperRef.current?.stopKeepAlive()
|
|
});
|
|
|
|
lifecycle.registerProcessHandlers();
|
|
registerKillSessionHandler(session.rpcHandlerManager, lifecycle.cleanupAndExit);
|
|
|
|
const syncSessionMode = () => {
|
|
const sessionInstance = sessionWrapperRef.current;
|
|
if (!sessionInstance) {
|
|
return;
|
|
}
|
|
const sessionModel = sessionInstance.getModel();
|
|
if (sessionModel !== undefined) {
|
|
currentModel = sessionModel ?? undefined;
|
|
}
|
|
sessionInstance.setPermissionMode(currentPermissionMode);
|
|
sessionInstance.setModel(currentModel ?? null);
|
|
sessionInstance.setCollaborationMode(currentCollaborationMode);
|
|
logger.debug(
|
|
`[Codex] Synced session config for keepalive: ` +
|
|
`permissionMode=${currentPermissionMode}, model=${currentModel ?? 'auto'}, collaborationMode=${currentCollaborationMode}`
|
|
);
|
|
};
|
|
|
|
session.onUserMessage((message) => {
|
|
const sessionPermissionMode = sessionWrapperRef.current?.getPermissionMode();
|
|
if (sessionPermissionMode && isPermissionModeAllowedForFlavor(sessionPermissionMode, 'codex')) {
|
|
currentPermissionMode = sessionPermissionMode as PermissionMode;
|
|
}
|
|
const sessionModel = sessionWrapperRef.current?.getModel();
|
|
if (sessionModel !== undefined) {
|
|
currentModel = sessionModel ?? undefined;
|
|
}
|
|
const sessionCollaborationMode = sessionWrapperRef.current?.getCollaborationMode();
|
|
if (sessionCollaborationMode) {
|
|
currentCollaborationMode = sessionCollaborationMode;
|
|
}
|
|
|
|
const messagePermissionMode = currentPermissionMode;
|
|
logger.debug(
|
|
`[Codex] User message received with permission mode: ${currentPermissionMode}, ` +
|
|
`model: ${currentModel ?? 'auto'}, collaborationMode: ${currentCollaborationMode}`
|
|
);
|
|
|
|
const enhancedMode: EnhancedMode = {
|
|
permissionMode: messagePermissionMode ?? 'default',
|
|
model: currentModel,
|
|
collaborationMode: currentCollaborationMode
|
|
};
|
|
const formattedText = formatMessageWithAttachments(message.content.text, message.content.attachments);
|
|
messageQueue.push(formattedText, enhancedMode);
|
|
});
|
|
|
|
const formatFailureReason = (message: string): string => {
|
|
const maxLength = 200;
|
|
if (message.length <= maxLength) {
|
|
return message;
|
|
}
|
|
return `${message.slice(0, maxLength)}...`;
|
|
};
|
|
|
|
const resolvePermissionMode = (value: unknown): PermissionMode => {
|
|
const parsed = PermissionModeSchema.safeParse(value);
|
|
if (!parsed.success || !isPermissionModeAllowedForFlavor(parsed.data, 'codex')) {
|
|
throw new Error('Invalid permission mode');
|
|
}
|
|
return parsed.data as PermissionMode;
|
|
};
|
|
|
|
const resolveCollaborationMode = (value: unknown): EnhancedMode['collaborationMode'] => {
|
|
if (value === null) {
|
|
return 'default';
|
|
}
|
|
const parsed = CodexCollaborationModeSchema.safeParse(value);
|
|
if (!parsed.success) {
|
|
throw new Error('Invalid collaboration mode');
|
|
}
|
|
return parsed.data;
|
|
};
|
|
|
|
session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => {
|
|
if (!payload || typeof payload !== 'object') {
|
|
throw new Error('Invalid session config payload');
|
|
}
|
|
const config = payload as { permissionMode?: unknown; collaborationMode?: unknown };
|
|
|
|
if (config.permissionMode !== undefined) {
|
|
currentPermissionMode = resolvePermissionMode(config.permissionMode);
|
|
}
|
|
|
|
if (config.collaborationMode !== undefined) {
|
|
currentCollaborationMode = resolveCollaborationMode(config.collaborationMode);
|
|
}
|
|
|
|
syncSessionMode();
|
|
return { applied: { permissionMode: currentPermissionMode, collaborationMode: currentCollaborationMode } };
|
|
});
|
|
|
|
try {
|
|
await loop({
|
|
path: workingDirectory,
|
|
startingMode,
|
|
messageQueue,
|
|
api,
|
|
session,
|
|
codexArgs: opts.codexArgs,
|
|
codexCliOverrides,
|
|
startedBy,
|
|
permissionMode: currentPermissionMode,
|
|
model: currentModel,
|
|
collaborationMode: currentCollaborationMode,
|
|
resumeSessionId: opts.resumeSessionId,
|
|
onModeChange: createModeChangeHandler(session),
|
|
onSessionReady: (instance) => {
|
|
sessionWrapperRef.current = instance;
|
|
syncSessionMode();
|
|
}
|
|
});
|
|
} catch (error) {
|
|
lifecycle.markCrash(error);
|
|
logger.debug('[codex] Loop error:', error);
|
|
} finally {
|
|
const localFailure = sessionWrapperRef.current?.localLaunchFailure;
|
|
if (localFailure?.exitReason === 'exit') {
|
|
lifecycle.setExitCode(1);
|
|
lifecycle.setArchiveReason(`Local launch failed: ${formatFailureReason(localFailure.message)}`);
|
|
}
|
|
await lifecycle.cleanupAndExit();
|
|
}
|
|
}
|