fix(codex): preserve native safety behavior (#1024)

* test: reproduce issue #1020

* fix: preserve Codex safety behavior (closes #1020)

* test: cover Codex safety buffering dismissal

* test: cover Codex safety retry edge cases

* fix: keep dismissed safety buffering prompts hidden
This commit is contained in:
SSU-WEI HUANG
2026-07-12 18:39:56 +08:00
committed by GitHub
parent d160203bb2
commit d9ea9db507
8 changed files with 1166 additions and 14 deletions
@@ -72,7 +72,12 @@ describe('AppServerEventConverter', () => {
expect(interrupted).toEqual([{ type: 'turn_aborted', turn_id: 'turn-1' }]);
const failed = converter.handleNotification('turn/completed', { turn: { id: 'turn-1' }, status: 'Failed', message: 'boom' });
expect(failed).toEqual([{ type: 'task_failed', turn_id: 'turn-1', error: 'boom' }]);
expect(failed).toEqual([{
type: 'task_failed',
turn_id: 'turn-1',
terminal_source: 'turn_completed',
error: 'boom'
}]);
});
it('accumulates agent message deltas', () => {
@@ -693,6 +698,120 @@ describe('AppServerEventConverter', () => {
expect(events).toEqual([{ type: 'task_failed', error: 'fatal' }]);
});
it('preserves typed non-retryable cyber-policy errors', () => {
const converter = new AppServerEventConverter();
const events = converter.handleNotification('error', {
threadId: 'thread-1',
turnId: 'turn-1',
error: {
message: 'This content was flagged for possible cybersecurity risk.',
codexErrorInfo: 'cyberPolicy'
},
willRetry: false
});
expect(events).toEqual([{
type: 'task_failed',
thread_id: 'thread-1',
turn_id: 'turn-1',
terminal_source: 'error',
retryable: false,
codex_error_info: 'cyberPolicy',
error: 'This content was flagged for possible cybersecurity risk.'
}]);
});
it('preserves cyber-policy metadata from wrapped and completed-turn errors', () => {
const converter = new AppServerEventConverter();
expect(converter.handleNotification('codex/event/error', {
msg: {
type: 'error',
thread_id: 'thread-1',
turn_id: 'turn-1',
message: 'wrapped policy failure',
codex_error_info: 'cyber_policy',
will_retry: false
}
})).toEqual([{
type: 'task_failed',
thread_id: 'thread-1',
turn_id: 'turn-1',
retryable: false,
codex_error_info: 'cyber_policy',
error: 'wrapped policy failure'
}]);
expect(converter.handleNotification('turn/completed', {
threadId: 'thread-1',
turn: {
id: 'turn-1',
status: 'failed',
error: {
message: 'completed policy failure',
codexErrorInfo: 'CyberPolicy'
}
}
})).toEqual([{
type: 'task_failed',
thread_id: 'thread-1',
turn_id: 'turn-1',
terminal_source: 'turn_completed',
codex_error_info: 'CyberPolicy',
error: 'completed policy failure'
}]);
});
it('maps Codex model safety notifications', () => {
const converter = new AppServerEventConverter();
expect(converter.handleNotification('model/safetyBuffering/updated', {
threadId: 'thread-1',
turnId: 'turn-1',
model: 'gpt-5.4',
useCases: ['cyber'],
reasons: ['review'],
showBufferingUi: true,
fasterModel: 'gpt-5.4-mini'
})).toEqual([{
type: 'model_safety_buffering',
thread_id: 'thread-1',
turn_id: 'turn-1',
model: 'gpt-5.4',
use_cases: ['cyber'],
reasons: ['review'],
show_buffering_ui: true,
faster_model: 'gpt-5.4-mini'
}]);
expect(converter.handleNotification('model/rerouted', {
threadId: 'thread-1',
turnId: 'turn-1',
fromModel: 'gpt-5.4',
toModel: 'gpt-5.4-codex',
reason: 'highRiskCyberActivity'
})).toEqual([{
type: 'model_rerouted',
thread_id: 'thread-1',
turn_id: 'turn-1',
from_model: 'gpt-5.4',
to_model: 'gpt-5.4-codex',
reason: 'highRiskCyberActivity'
}]);
expect(converter.handleNotification('model/verification', {
threadId: 'thread-1',
turnId: 'turn-1',
verifications: ['trustedAccessForCyber']
})).toEqual([{
type: 'model_verification',
thread_id: 'thread-1',
turn_id: 'turn-1',
verifications: ['trustedAccessForCyber']
}]);
});
it('maps thread/compacted notifications', () => {
const converter = new AppServerEventConverter();
const events = converter.handleNotification('thread/compacted', {