--- a/crates/airouter-agent-client/src/native/codex.rs +++ b/crates/airouter-agent-client/src/native/codex.rs @@ -159,6 +159,7 @@ input: item .get("input") .cloned() + .or_else(|| item.get("arguments").cloned()) .or_else(|| item.get("args").cloned()) .unwrap_or_else(|| json!({})), }] --- a/crates/airouter-agent-client/tests/native/codex_stream.rs +++ b/crates/airouter-agent-client/tests/native/codex_stream.rs @@ -76,6 +76,25 @@ tool: "mcp__workbench__memory_get".to_string(), output: "{\"ok\":true}".to_string(), exit_code: 0, + }] + ); +} + +#[test] +fn preserves_native_mcp_arguments_for_execution_receipt_matching() { + let mut parser = CodexStreamParser::new(super::hooks()); + let arguments = json!({"host":"fixture","command":"id","approved":true}); + assert_eq!( + parser.ingest_value(&json!({ + "type":"item.started", + "item":{"type":"mcp_tool_call","id":"repair-check", + "server":"workbench","tool":"admin_diagnostic_command", + "arguments":arguments,"status":"in_progress"} + })), + vec![ProviderStreamEvent::ToolCall { + tool_use_id: "repair-check".into(), + tool: "admin_diagnostic_command".into(), + input: arguments, }] ); } --- a/scripts/qualify-native-session-service.py +++ b/scripts/qualify-native-session-service.py @@ -147,6 +147,10 @@ def emit(value): print(json.dumps(value), flush=True) with open('effects', 'a') as effect: effect.write('x') emit({'type':'item.started','item':{'type':'agent_message','id':'answer'}}) +emit({'type':'item.started','item':{'type':'mcp_tool_call','id':'receipt-check', + 'tool':'admin_diagnostic_command','arguments':{'host':'fixture','command':'id'},'status':'in_progress'}}) +emit({'type':'item.completed','item':{'type':'mcp_tool_call','id':'receipt-check', + 'tool':'admin_diagnostic_command','result':{'isError':False,'content':[]},'status':'completed'}}) pathlib.Path('started').touch() until = time.monotonic() + 30 while not pathlib.Path('release').exists(): @@ -170,18 +174,25 @@ wait(lambda: request(endpoint, 'GET', run_route)['state'] == 'finished') result = request(endpoint, 'GET', run_route) assert result['phase'] == 'complete' and result['error'] is None and result['activity_complete'], result + activity = request(endpoint, 'GET', run_route + '/events?after=0') + calls = [e['Activity']['ToolCall'] for e in activity['events'] + if 'ToolCall' in e.get('Activity', {})] + assert calls == [{'tool_use_id': 'receipt-check', 'tool': 'admin_diagnostic_command', + 'input': {'host': 'fixture', 'command': 'id'}}], calls stop(daemon) assert daemon.returncode == 0 daemon = launch(command, 'standalone-sessiond-restarted.log', cgroup=True) wait(endpoint.exists) recovered = request(endpoint, 'POST', '/v1/sessions', body) assert recovered == result + assert request(endpoint, 'GET', run_route + '/events?after=0') == activity assert (root / 'workspace/effects').read_text() == 'x', 'native effect repeated' stop(daemon) stop(controller, signal.SIGINT) assert daemon.returncode == 0 and controller.returncode == 0 receipt.update(passed=True, status=result, executions=1, restart_preserved_result=True, - observer_disconnect_preserved_execution=True) + observer_disconnect_preserved_execution=True, + native_mcp_arguments_preserved=True, restart_preserved_tool_receipts=True) finally: for process in reversed(processes): stop(process)