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
+90 -7
View File
@@ -306,6 +306,18 @@ function extractStringArray(value: unknown): string[] {
: [];
}
function extractCodexErrorInfo(
record: Record<string, unknown>,
errorRecord: Record<string, unknown> | null
): string | null {
return asString(
record.codexErrorInfo
?? record.codex_error_info
?? errorRecord?.codexErrorInfo
?? errorRecord?.codex_error_info
);
}
function buildCollabAgentInput(item: Record<string, unknown>, toolName: string): Record<string, unknown> {
const targets = extractStringArray(item.receiverThreadIds ?? item.receiver_thread_ids ?? item.targets);
const input: Record<string, unknown> = {};
@@ -526,12 +538,19 @@ export class AppServerEventConverter {
if (msgType === 'error') {
const errorRecord = asRecord(msg.error);
const willRetry = asBoolean(msg.will_retry ?? msg.willRetry ?? errorRecord?.will_retry ?? errorRecord?.willRetry) ?? false;
const retryable = asBoolean(msg.will_retry ?? msg.willRetry ?? errorRecord?.will_retry ?? errorRecord?.willRetry);
const willRetry = retryable ?? false;
if (willRetry) {
return [];
}
const error = asString(msg.message ?? msg.reason ?? errorRecord?.message);
return error ? addEventScope([{ type: 'task_failed', error }], msgScope) : [];
const codexErrorInfo = extractCodexErrorInfo(msg, errorRecord);
return error ? addEventScope([{
type: 'task_failed',
...(retryable !== null ? { retryable } : {}),
...(codexErrorInfo ? { codex_error_info: codexErrorInfo } : {}),
error
}], msgScope) : [];
}
if (msgType === 'plan_update') {
@@ -672,7 +691,9 @@ export class AppServerEventConverter {
const statusRaw = asString(paramsRecord.status ?? turn.status);
const status = statusRaw?.toLowerCase();
const turnId = asString(turn.turnId ?? turn.turn_id ?? turn.id);
const errorMessage = asString(paramsRecord.error ?? paramsRecord.message ?? paramsRecord.reason);
const turnError = asRecord(paramsRecord.error ?? turn.error);
const errorMessage = asString(paramsRecord.error ?? paramsRecord.message ?? paramsRecord.reason)
?? asString(turnError?.message);
if (status === 'interrupted' || status === 'cancelled' || status === 'canceled') {
events.push(scoped({ type: 'turn_aborted', ...(turnId ? { turn_id: turnId } : {}) }));
@@ -680,7 +701,14 @@ export class AppServerEventConverter {
}
if (status === 'failed' || status === 'error') {
events.push(scoped({ type: 'task_failed', ...(turnId ? { turn_id: turnId } : {}), ...(errorMessage ? { error: errorMessage } : {}) }));
const codexErrorInfo = extractCodexErrorInfo(paramsRecord, turnError);
events.push(scoped({
type: 'task_failed',
...(turnId ? { turn_id: turnId } : {}),
terminal_source: 'turn_completed',
...(codexErrorInfo ? { codex_error_info: codexErrorInfo } : {}),
...(errorMessage ? { error: errorMessage } : {})
}));
return events;
}
@@ -702,12 +730,66 @@ export class AppServerEventConverter {
return events;
}
if (method === 'model/safetyBuffering/updated') {
const model = asString(paramsRecord.model);
const showBufferingUi = asBoolean(paramsRecord.showBufferingUi ?? paramsRecord.show_buffering_ui);
if (!model || showBufferingUi === null) {
return events;
}
events.push(scoped({
type: 'model_safety_buffering',
model,
use_cases: extractStringArray(paramsRecord.useCases ?? paramsRecord.use_cases),
reasons: extractStringArray(paramsRecord.reasons),
show_buffering_ui: showBufferingUi,
faster_model: asString(paramsRecord.fasterModel ?? paramsRecord.faster_model)
}));
return events;
}
if (method === 'model/rerouted') {
const fromModel = asString(paramsRecord.fromModel ?? paramsRecord.from_model);
const toModel = asString(paramsRecord.toModel ?? paramsRecord.to_model);
const reason = asString(paramsRecord.reason);
if (fromModel && toModel && reason) {
events.push(scoped({
type: 'model_rerouted',
from_model: fromModel,
to_model: toModel,
reason
}));
}
return events;
}
if (method === 'model/verification') {
events.push(scoped({
type: 'model_verification',
verifications: extractStringArray(paramsRecord.verifications)
}));
return events;
}
if (method === 'error') {
const willRetry = asBoolean(paramsRecord.will_retry ?? paramsRecord.willRetry) ?? false;
const errorRecord = asRecord(paramsRecord.error);
const retryable = asBoolean(
paramsRecord.will_retry
?? paramsRecord.willRetry
?? errorRecord?.will_retry
?? errorRecord?.willRetry
);
const willRetry = retryable ?? false;
if (willRetry) return events;
const message = asString(paramsRecord.message) ?? asString(asRecord(paramsRecord.error)?.message);
const message = asString(paramsRecord.message) ?? asString(errorRecord?.message);
if (message) {
events.push(scoped({ type: 'task_failed', error: message }));
const codexErrorInfo = extractCodexErrorInfo(paramsRecord, errorRecord);
events.push(scoped({
type: 'task_failed',
terminal_source: 'error',
...(retryable !== null ? { retryable } : {}),
...(codexErrorInfo ? { codex_error_info: codexErrorInfo } : {}),
error: message
}));
}
return events;
}
@@ -723,6 +805,7 @@ export class AppServerEventConverter {
this.lastAgentMessageDeltaByItemId.set(itemId, delta);
const prev = this.agentMessageBuffers.get(itemId) ?? '';
this.agentMessageBuffers.set(itemId, prev + delta);
events.push(scoped({ type: 'agent_message_delta' }));
}
return events;
}