mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
fix(codex): preserve plan approval in yolo (#1420)
This commit is contained in:
@@ -399,13 +399,13 @@ describe('appServerConfig', () => {
|
||||
expect(params.collaborationMode).toBeUndefined();
|
||||
});
|
||||
|
||||
it('puts collaboration mode in turn params with model settings', () => {
|
||||
it('keeps yolo access while using Codex built-in plan instructions', () => {
|
||||
const params = buildTurnStartParams({
|
||||
threadId: 'thread-1',
|
||||
message: 'hello',
|
||||
cwd: '/workspace/project',
|
||||
mode: {
|
||||
permissionMode: 'default',
|
||||
permissionMode: 'yolo',
|
||||
model: 'o3',
|
||||
modelReasoningEffort: 'high',
|
||||
collaborationMode: 'plan'
|
||||
@@ -417,13 +417,14 @@ describe('appServerConfig', () => {
|
||||
settings: {
|
||||
model: 'o3',
|
||||
reasoning_effort: 'high',
|
||||
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
||||
developer_instructions: null
|
||||
}
|
||||
});
|
||||
expect(params.sandboxPolicy).toEqual({ type: 'dangerFullAccess' });
|
||||
expect(params.model).toBeUndefined();
|
||||
});
|
||||
|
||||
it('carries custom developer instructions into collaboration mode settings', () => {
|
||||
it('does not override Codex built-in plan instructions', () => {
|
||||
const params = buildTurnStartParams({
|
||||
threadId: 'thread-1',
|
||||
message: 'hello',
|
||||
@@ -436,7 +437,7 @@ describe('appServerConfig', () => {
|
||||
mode: 'plan',
|
||||
settings: {
|
||||
model: 'o3',
|
||||
developer_instructions: withCollaborationInstructions(`${codexSystemPrompt}\n\nOnly respond in Chinese.`)
|
||||
developer_instructions: null
|
||||
}
|
||||
});
|
||||
expect(params.collaborationMode?.settings).not.toHaveProperty('reasoning_effort');
|
||||
|
||||
@@ -298,13 +298,14 @@ export function buildTurnStartParams(args: {
|
||||
if (!model) {
|
||||
throw new Error(`Collaboration mode '${collaborationMode}' requires a resolved model`);
|
||||
}
|
||||
const { developerInstructions } = resolveInstructions(args);
|
||||
params.collaborationMode = {
|
||||
mode: collaborationMode,
|
||||
settings: {
|
||||
model,
|
||||
...(modelReasoningEffort !== undefined ? { reasoning_effort: modelReasoningEffort } : {}),
|
||||
developer_instructions: appendCollaborationInstructions(developerInstructions, args.mode?.proactiveMultiAgent)
|
||||
developer_instructions: collaborationMode === 'plan'
|
||||
? null
|
||||
: appendCollaborationInstructions(resolveInstructions(args).developerInstructions, args.mode?.proactiveMultiAgent)
|
||||
}
|
||||
};
|
||||
} else if (model) {
|
||||
|
||||
@@ -7,7 +7,10 @@ type FakeAgentState = {
|
||||
completedRequests: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function createHarness(mode: 'default' | 'read-only' | 'safe-yolo' | 'yolo') {
|
||||
function createHarness(
|
||||
mode: 'default' | 'read-only' | 'safe-yolo' | 'yolo',
|
||||
collaborationMode?: 'default' | 'plan'
|
||||
) {
|
||||
let agentState: FakeAgentState = {
|
||||
requests: {},
|
||||
completedRequests: {}
|
||||
@@ -25,7 +28,9 @@ function createHarness(mode: 'default' | 'read-only' | 'safe-yolo' | 'yolo') {
|
||||
}
|
||||
} as unknown as ApiSessionClient;
|
||||
|
||||
const handler = new CodexPermissionHandler(session, () => mode);
|
||||
const handler = new CodexPermissionHandler(session, () => mode, {
|
||||
getCollaborationMode: () => collaborationMode
|
||||
});
|
||||
|
||||
return {
|
||||
handler,
|
||||
@@ -69,6 +74,23 @@ describe('CodexPermissionHandler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps plan exit pending in yolo until the user approves it', async () => {
|
||||
const { handler, rpcHandlers, getAgentState } = createHarness('yolo', 'plan');
|
||||
const resultPromise = handler.handleToolCall('plan-exit', 'exit_plan_mode', { plan: '1. Implement' });
|
||||
|
||||
expect(getAgentState().requests).toMatchObject({
|
||||
'plan-exit': { tool: 'exit_plan_mode' }
|
||||
});
|
||||
|
||||
await rpcHandlers.get('permission')?.({
|
||||
id: 'plan-exit',
|
||||
approved: true,
|
||||
decision: 'approved'
|
||||
});
|
||||
|
||||
await expect(resultPromise).resolves.toMatchObject({ decision: 'approved' });
|
||||
});
|
||||
|
||||
it('auto-approves safe-yolo requests once', async () => {
|
||||
const { handler, getAgentState } = createHarness('safe-yolo');
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ type UserInputResult = {
|
||||
type PermissionResult = ToolPermissionResult | UserInputResult;
|
||||
|
||||
type CodexPermissionHandlerOptions = {
|
||||
getCollaborationMode?: () => 'default' | 'plan' | undefined;
|
||||
onRequest?: (request: { id: string; toolName: string; input: unknown }) => void;
|
||||
onComplete?: (result: {
|
||||
id: string;
|
||||
@@ -110,7 +111,11 @@ export class CodexPermissionHandler extends BasePermissionHandler<PermissionResp
|
||||
input: unknown
|
||||
): Promise<ToolPermissionResult> {
|
||||
const mode = this.getPermissionMode() ?? 'default';
|
||||
const autoDecision = this.resolveAutoApprovalDecision(mode, toolName, toolCallId);
|
||||
const requiresPlanApproval = this.options?.getCollaborationMode?.() === 'plan'
|
||||
&& (toolName === 'exit_plan_mode' || toolName === 'ExitPlanMode');
|
||||
const autoDecision = requiresPlanApproval
|
||||
? null
|
||||
: this.resolveAutoApprovalDecision(mode, toolName, toolCallId);
|
||||
if (autoDecision) {
|
||||
return Promise.resolve(this.completeAutoApproval(toolCallId, toolName, input, autoDecision));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user